mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-23 12:14:10 +08:00
MMC core:
- Add documentation for the mmc-test driver - Register the eMMC RPMB partition with the RPMB subsystem - Some various cleanups MMC host: - dw_mmc-rockchip: Add support for the RK3576 variant - renesas_sdhi: Add support for the RZ/V2H(P) variant - sdhci_am654: Add a retry mechanism for tuning - sdhci-atmel: Convert DT bindings to json schema - sdhci-of-dwcmshc: Add eMMC HW reset support for BlueField-3 SoC - sdhci-of-dwcmshc: Add support for the RK3576 variant - sdhci-of-dwcmshc: Add support for the Sophgo SG2042 variant - sdhci-of-ma35d1: Add new driver for the Nuvoton MA35D1 SDHCI Misc/Tee: - Add Replay Protected Memory Block (RPMB) subsystem - Let optee probe RPMB device using RPMB subsystem -----BEGIN PGP SIGNATURE----- iQJLBAABCgA1FiEEugLDXPmKSktSkQsV/iaEJXNYjCkFAmboQbkXHHVsZi5oYW5z c29uQGxpbmFyby5vcmcACgkQ/iaEJXNYjClXCBAAg1X6Q+4Mx4LZszDWeSDQfH4d dbD3RDz8vTBCGrOzZT5Ak+0d3aSeJv5Fxz2XTp4W8D5KXm8HdYC1x7c81StL104k yu5xG53u3D9qerwqaw/EGPqW9LUpsMRBRepbHlI9pH7hfmv9oqZXHl9hRnnDDrDJ FMNz9PrUdEUCk8RP9StyYIKUpe1VsIlBa1xNXXuDqt3d70UgX7odIOGOXdWv12CV zSB7Y7FlNBk1A/EJ4ptrfOc3RnCOtq1+j2D5TrOQ16rPC5Ud55XMNjrRir57nl3O ikd/hXUc7cklbYMv1K61aSAv0mTODkZ2P78buP/6dz1NUeFeoXkYfFR2eTeCDNdD H+TieLw2g5p0cDEq21K4V6ZyzC3kh8qhrza9EXVgFfg6WkbxHxFzSb9heOr1K8pn 2yOLaDRlzHWPU6jOIMHeZgnvWOIVcIlHGu7qMKTDsnJh2Ot+HPjsNoToiWnCEvvs UbfWRfcdLCxkmqHS3dROJ8d6rPa1bDXy4/MZxjDzhKCarW1ZS2n4N55qeuM6Fb9s VjT43HlqgA/Jlo9zUDvkDG4Zblo22zQRBsyccqD8W4T+hnR917NA7/808ZogFdHX WjHYYWkCOjaZcVv5T9ZztVm0nTgeeM1/cAPyo8JlbVtBX2ZBJtiIW6bqWxGCw+Vw vxI5ht9AeVDTHixKK+U= =en1i -----END PGP SIGNATURE----- Merge tag 'mmc-v6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc Pull MMC updates from Ulf Hansson: "MMC core: - Add documentation for the mmc-test driver - Register the eMMC RPMB partition with the RPMB subsystem - Some various cleanups MMC host: - dw_mmc-rockchip: Add support for the RK3576 variant - renesas_sdhi: Add support for the RZ/V2H(P) variant - sdhci_am654: Add a retry mechanism for tuning - sdhci-atmel: Convert DT bindings to json schema - sdhci-of-dwcmshc: - Add eMMC HW reset support for BlueField-3 SoC - Add support for the RK3576 variant - Add support for the Sophgo SG2042 variant - sdhci-of-ma35d1: Add new driver for the Nuvoton MA35D1 SDHCI Misc/Tee: - Add Replay Protected Memory Block (RPMB) subsystem - Let optee probe RPMB device using RPMB subsystem" * tag 'mmc-v6.12' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc: (41 commits) mmc: core: Use dev_err_probe for deferred regulators optee: Fix a NULL vs IS_ERR() check mmc: sdhci_am654: Add prints to tuning algorithm mmc: sdhci_am654: Add retry tuning dt-bindings: mmc: Add support for rk3576 eMMC Documentation: mmc: Add mmc-test doc rpmb: fix error path in rpmb_dev_register() optee: add RPMB dependency mmc: block: add RPMB dependency mmc: core Convert UNSTUFF_BITS macro to inline function dt-bindings: mmc: sdhci-atmel: Convert to json schema mmc: core: Convert simple_stroul to kstroul mmc: core: Calculate size from pointer mmc: cqhci: Make use of cqhci_halted() routine mmc: core: Replace the argument of mmc_sd_switch() with defines mmc: dw_mmc-rockchip: Add support for rk3576 SoCs mmc: dw_mmc-rockchip: Add internal phase support dt-bindings: mmc: Add support for rk3576 dw-mshc mmc: sdhci-of-dwcmshc: Add hw_reset() support for BlueField-3 SoC mmc: core: remove left-over data structure declarations ...
This commit is contained in:
commit
7fced2a78a
15
Documentation/ABI/testing/sysfs-class-tee
Normal file
15
Documentation/ABI/testing/sysfs-class-tee
Normal file
@ -0,0 +1,15 @@
|
||||
What: /sys/class/tee/tee{,priv}X/rpmb_routing_model
|
||||
Date: May 2024
|
||||
KernelVersion: 6.10
|
||||
Contact: op-tee@lists.trustedfirmware.org
|
||||
Description:
|
||||
RPMB frames can be routed to the RPMB device via the
|
||||
user-space daemon tee-supplicant or the RPMB subsystem
|
||||
in the kernel. The value "user" means that the driver
|
||||
will route the RPMB frames via user space. Conversely,
|
||||
"kernel" means that the frames are routed via the RPMB
|
||||
subsystem without assistance from tee-supplicant. It
|
||||
should be assumed that RPMB frames are routed via user
|
||||
space if the variable is absent. The primary purpose
|
||||
of this variable is to let systemd know whether
|
||||
tee-supplicant is needed in the early boot with initramfs.
|
@ -0,0 +1,92 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mmc/atmel,sama5d2-sdhci.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Atmel SDHCI controller
|
||||
|
||||
maintainers:
|
||||
- Aubin Constans <aubin.constans@microchip.com>
|
||||
- Nicolas Ferre <nicolas.ferre@microchip.com>
|
||||
|
||||
description:
|
||||
Bindings for the SDHCI controller found in Atmel/Microchip SoCs.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- enum:
|
||||
- atmel,sama5d2-sdhci
|
||||
- microchip,sam9x60-sdhci
|
||||
- items:
|
||||
- enum:
|
||||
- microchip,sam9x7-sdhci
|
||||
- microchip,sama7g5-sdhci
|
||||
- const: microchip,sam9x60-sdhci
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: hclock
|
||||
- description: multclk
|
||||
- description: baseclk
|
||||
minItems: 2
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: hclock
|
||||
- const: multclk
|
||||
- const: baseclk
|
||||
minItems: 2
|
||||
|
||||
microchip,sdcal-inverted:
|
||||
type: boolean
|
||||
description:
|
||||
When present, polarity on the SDCAL SoC pin is inverted. The default
|
||||
polarity for this signal is described in the datasheet. For instance on
|
||||
SAMA5D2, the pin is usually tied to the GND with a resistor and a
|
||||
capacitor (see "SDMMC I/O Calibration" chapter).
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
allOf:
|
||||
- $ref: sdhci-common.yaml#
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- atmel,sama5d2-sdhci
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 3
|
||||
clock-names:
|
||||
minItems: 3
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
#include <dt-bindings/clock/at91.h>
|
||||
mmc@a0000000 {
|
||||
compatible = "atmel,sama5d2-sdhci";
|
||||
reg = <0xa0000000 0x300>;
|
||||
interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>;
|
||||
clocks = <&sdmmc0_hclk>, <&sdmmc0_gclk>, <&main>;
|
||||
clock-names = "hclock", "multclk", "baseclk";
|
||||
assigned-clocks = <&sdmmc0_gclk>;
|
||||
assigned-clock-rates = <480000000>;
|
||||
};
|
@ -0,0 +1,87 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/mmc/nuvoton,ma35d1-sdhci.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Nuvoton MA35D1 SD/SDIO/MMC Controller
|
||||
|
||||
maintainers:
|
||||
- Shan-Chun Hung <shanchun1218@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: sdhci-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- nuvoton,ma35d1-sdhci
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
pinctrl-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: default
|
||||
- const: state_uhs
|
||||
|
||||
pinctrl-0:
|
||||
description:
|
||||
Should contain default/high speed pin ctrl.
|
||||
maxItems: 1
|
||||
|
||||
pinctrl-1:
|
||||
description:
|
||||
Should contain uhs mode pin ctrl.
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 1
|
||||
|
||||
nuvoton,sys:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description: phandle to access GCR (Global Control Register) registers.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- pinctrl-names
|
||||
- pinctrl-0
|
||||
- resets
|
||||
- nuvoton,sys
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
|
||||
#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
|
||||
|
||||
soc {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
mmc@40190000 {
|
||||
compatible = "nuvoton,ma35d1-sdhci";
|
||||
reg = <0x0 0x40190000 0x0 0x2000>;
|
||||
interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&clk SDH1_GATE>;
|
||||
pinctrl-names = "default", "state_uhs";
|
||||
pinctrl-0 = <&pinctrl_sdhci1>;
|
||||
pinctrl-1 = <&pinctrl_sdhci1_uhs>;
|
||||
resets = <&sys MA35D1_RESET_SDH1>;
|
||||
nuvoton,sys = <&sys>;
|
||||
vqmmc-supply = <&sdhci1_vqmmc_regulator>;
|
||||
bus-width = <8>;
|
||||
max-frequency = <200000000>;
|
||||
};
|
||||
};
|
@ -18,6 +18,7 @@ properties:
|
||||
- renesas,sdhi-r7s9210 # SH-Mobile AG5
|
||||
- renesas,sdhi-r8a73a4 # R-Mobile APE6
|
||||
- renesas,sdhi-r8a7740 # R-Mobile A1
|
||||
- renesas,sdhi-r9a09g057 # RZ/V2H(P)
|
||||
- renesas,sdhi-sh73a0 # R-Mobile APE6
|
||||
- items:
|
||||
- enum:
|
||||
@ -75,9 +76,13 @@ properties:
|
||||
minItems: 1
|
||||
maxItems: 3
|
||||
|
||||
clocks: true
|
||||
clocks:
|
||||
minItems: 1
|
||||
maxItems: 4
|
||||
|
||||
clock-names: true
|
||||
clock-names:
|
||||
minItems: 1
|
||||
maxItems: 4
|
||||
|
||||
dmas:
|
||||
minItems: 4
|
||||
@ -118,7 +123,9 @@ allOf:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: renesas,rzg2l-sdhi
|
||||
enum:
|
||||
- renesas,sdhi-r9a09g057
|
||||
- renesas,rzg2l-sdhi
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
|
@ -43,6 +43,8 @@ properties:
|
||||
- rockchip,rv1108-dw-mshc
|
||||
- rockchip,rv1126-dw-mshc
|
||||
- const: rockchip,rk3288-dw-mshc
|
||||
# for Rockchip RK3576 with phase tuning inside the controller
|
||||
- const: rockchip,rk3576-dw-mshc
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
@ -1,35 +0,0 @@
|
||||
* Atmel SDHCI controller
|
||||
|
||||
This file documents the differences between the core properties in
|
||||
Documentation/devicetree/bindings/mmc/mmc.txt and the properties used by the
|
||||
sdhci-of-at91 driver.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "atmel,sama5d2-sdhci" or "microchip,sam9x60-sdhci"
|
||||
or "microchip,sam9x7-sdhci", "microchip,sam9x60-sdhci".
|
||||
- clocks: Phandlers to the clocks.
|
||||
- clock-names: Must be "hclock", "multclk", "baseclk" for
|
||||
"atmel,sama5d2-sdhci".
|
||||
Must be "hclock", "multclk" for "microchip,sam9x60-sdhci".
|
||||
Must be "hclock", "multclk" for "microchip,sam9x7-sdhci".
|
||||
|
||||
Optional properties:
|
||||
- assigned-clocks: The same with "multclk".
|
||||
- assigned-clock-rates The rate of "multclk" in order to not rely on the
|
||||
gck configuration set by previous components.
|
||||
- microchip,sdcal-inverted: when present, polarity on the SDCAL SoC pin is
|
||||
inverted. The default polarity for this signal is described in the datasheet.
|
||||
For instance on SAMA5D2, the pin is usually tied to the GND with a resistor
|
||||
and a capacitor (see "SDMMC I/O Calibration" chapter).
|
||||
|
||||
Example:
|
||||
|
||||
mmc0: sdio-host@a0000000 {
|
||||
compatible = "atmel,sama5d2-sdhci";
|
||||
reg = <0xa0000000 0x300>;
|
||||
interrupts = <31 IRQ_TYPE_LEVEL_HIGH 0>;
|
||||
clocks = <&sdmmc0_hclk>, <&sdmmc0_gclk>, <&main>;
|
||||
clock-names = "hclock", "multclk", "baseclk";
|
||||
assigned-clocks = <&sdmmc0_gclk>;
|
||||
assigned-clock-rates = <480000000>;
|
||||
};
|
@ -10,17 +10,19 @@ maintainers:
|
||||
- Ulf Hansson <ulf.hansson@linaro.org>
|
||||
- Jisheng Zhang <Jisheng.Zhang@synaptics.com>
|
||||
|
||||
allOf:
|
||||
- $ref: mmc-controller.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
oneOf:
|
||||
- items:
|
||||
- const: rockchip,rk3576-dwcmshc
|
||||
- const: rockchip,rk3588-dwcmshc
|
||||
- enum:
|
||||
- rockchip,rk3568-dwcmshc
|
||||
- rockchip,rk3588-dwcmshc
|
||||
- snps,dwcmshc-sdhci
|
||||
- sophgo,cv1800b-dwcmshc
|
||||
- sophgo,sg2002-dwcmshc
|
||||
- sophgo,sg2042-dwcmshc
|
||||
- thead,th1520-dwcmshc
|
||||
|
||||
reg:
|
||||
@ -31,22 +33,14 @@ properties:
|
||||
|
||||
clocks:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: core clock
|
||||
- description: bus clock for optional
|
||||
- description: axi clock for rockchip specified
|
||||
- description: block clock for rockchip specified
|
||||
- description: timer clock for rockchip specified
|
||||
|
||||
maxItems: 5
|
||||
|
||||
clock-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: core
|
||||
- const: bus
|
||||
- const: axi
|
||||
- const: block
|
||||
- const: timer
|
||||
maxItems: 5
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
resets:
|
||||
maxItems: 5
|
||||
@ -63,7 +57,6 @@ properties:
|
||||
description: Specify the number of delay for tx sampling.
|
||||
$ref: /schemas/types.yaml#/definitions/uint8
|
||||
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
@ -71,6 +64,60 @@ required:
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
allOf:
|
||||
- $ref: mmc-controller.yaml#
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: sophgo,sg2042-dwcmshc
|
||||
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
items:
|
||||
- description: core clock
|
||||
- description: bus clock
|
||||
- description: timer clock
|
||||
clock-names:
|
||||
items:
|
||||
- const: core
|
||||
- const: bus
|
||||
- const: timer
|
||||
else:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: core clock
|
||||
- description: bus clock for optional
|
||||
- description: axi clock for rockchip specified
|
||||
- description: block clock for rockchip specified
|
||||
- description: timer clock for rockchip specified
|
||||
clock-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: core
|
||||
- const: bus
|
||||
- const: axi
|
||||
- const: block
|
||||
- const: timer
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: rockchip,rk3576-dwcmshc
|
||||
|
||||
then:
|
||||
required:
|
||||
- power-domains
|
||||
|
||||
else:
|
||||
properties:
|
||||
power-domains: false
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
|
@ -10,4 +10,5 @@ MMC/SD/SDIO card support
|
||||
mmc-dev-attrs
|
||||
mmc-dev-parts
|
||||
mmc-async-req
|
||||
mmc-test
|
||||
mmc-tools
|
||||
|
299
Documentation/driver-api/mmc/mmc-test.rst
Normal file
299
Documentation/driver-api/mmc/mmc-test.rst
Normal file
@ -0,0 +1,299 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
========================
|
||||
MMC Test Framework
|
||||
========================
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
The `mmc_test` framework is designed to test the performance and reliability of host controller drivers and all devices handled by the MMC subsystem. This includes not only MMC devices but also SD cards and other devices supported by the subsystem.
|
||||
|
||||
The framework provides a variety of tests to evaluate different aspects of the host controller and device interactions, such as read and write performance, data integrity, and error handling. These tests help ensure that the host controller drivers and devices operate correctly under various conditions.
|
||||
|
||||
The `mmc_test` framework is particularly useful for:
|
||||
|
||||
- Verifying the functionality and performance of MMC and SD host controller drivers.
|
||||
- Ensuring compatibility and reliability of MMC and SD devices.
|
||||
- Identifying and diagnosing issues in the MMC subsystem.
|
||||
|
||||
The results of the tests are logged in the kernel log, providing detailed information about the test outcomes and any encountered issues.
|
||||
|
||||
Note: whatever is on your card will be overwritten by these tests.
|
||||
|
||||
Initialization
|
||||
==============
|
||||
|
||||
To use the ``mmc_test`` framework, follow these steps:
|
||||
|
||||
1. **Enable the MMC Test Framework**:
|
||||
|
||||
Ensure that the ``CONFIG_MMC_TEST`` kernel configuration option is enabled. This can be done by configuring the kernel:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
make menuconfig
|
||||
|
||||
Navigate to:
|
||||
|
||||
Device Drivers --->
|
||||
<*> MMC/SD/SDIO card support --->
|
||||
[*] MMC host test driver
|
||||
|
||||
Alternatively, you can enable it directly in the kernel configuration file:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
echo "CONFIG_MMC_TEST=y" >> .config
|
||||
|
||||
Rebuild and install the kernel if necessary.
|
||||
|
||||
2. **Load the MMC Test Module**:
|
||||
|
||||
If the ``mmc_test`` framework is built as a module, you need to load it using ``modprobe``:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
modprobe mmc_test
|
||||
|
||||
Binding the MMC Card for Testing
|
||||
================================
|
||||
|
||||
To enable MMC testing, you need to unbind the MMC card from the ``mmcblk`` driver and bind it to the ``mmc_test`` driver. This allows the ``mmc_test`` framework to take control of the MMC card for testing purposes.
|
||||
|
||||
1. Identify the MMC card:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
ls /sys/bus/mmc/devices/
|
||||
|
||||
This will list the MMC devices, such as ``mmc0:0001``.
|
||||
|
||||
2. Unbind the MMC card from the ``mmcblk`` driver:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo 'mmc0:0001' > /sys/bus/mmc/drivers/mmcblk/unbind
|
||||
|
||||
3. Bind the MMC card to the ``mmc_test`` driver:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo 'mmc0:0001' > /sys/bus/mmc/drivers/mmc_test/bind
|
||||
|
||||
After binding, you should see a line in the kernel log indicating that the card has been claimed for testing:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mmc_test mmc0:0001: Card claimed for testing.
|
||||
|
||||
|
||||
Usage - Debugfs Entries
|
||||
=======================
|
||||
|
||||
Once the ``mmc_test`` framework is enabled, you can interact with the following debugfs entries located in ``/sys/kernel/debug/mmc0/mmc0:0001``:
|
||||
|
||||
1. **test**:
|
||||
|
||||
This file is used to run specific tests. Write the test number to this file to execute a test.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo <test_number> > /sys/kernel/debug/mmc0/mmc0:0001/test
|
||||
|
||||
The test result is indicated in the kernel log info. You can view the kernel log using the `dmesg` command or by checking the log file in `/var/log/`.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
dmesg | grep mmc0
|
||||
|
||||
Example:
|
||||
|
||||
To run test number 4 (Basic read with data verification):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo 4 > /sys/kernel/debug/mmc0/mmc0:0001/test
|
||||
|
||||
Check the kernel log for the result:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
dmesg | grep mmc0
|
||||
|
||||
2. **testlist**:
|
||||
|
||||
This file lists all available tests. You can read this file to see the list of tests and their corresponding numbers.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cat /sys/kernel/debug/mmc0/mmc0:0001/testlist
|
||||
|
||||
The available tests are listed in the table below:
|
||||
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| Test | Test Name | Test Description |
|
||||
+======+================================+=============================================+
|
||||
| 0 | Run all tests | Runs all available tests |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 1 | Basic write | Performs a basic write operation of a |
|
||||
| | | single 512-Byte block to the MMC card |
|
||||
| | | without data verification. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 2 | Basic read | Same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 3 | Basic write | Performs a basic write operation of a |
|
||||
| | (with data verification) | single 512-Byte block to the MMC card |
|
||||
| | | with data verification by reading back |
|
||||
| | | the written data and comparing it. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 4 | Basic read | Same for read |
|
||||
| | (with data verification) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 5 | Multi-block write | Performs a multi-block write operation of |
|
||||
| | | 8 blocks (each 512 bytes) to the MMC card. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 6 | Multi-block read | Same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 7 | Power of two block writes | Performs write operations with block sizes |
|
||||
| | | that are powers of two, starting from 1 |
|
||||
| | | byte up to 256 bytes, to the MMC card. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 8 | Power of two block reads | Same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 9 | Weird sized block writes | Performs write operations with varying |
|
||||
| | | block sizes starting from 3 bytes and |
|
||||
| | | increasing by 7 bytes each iteration, up |
|
||||
| | | to 511 bytes, to the MMC card. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 10 | Weird sized block reads | same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 11 | Badly aligned write | Performs write operations with buffers |
|
||||
| | | starting at different alignments (0 to 7 |
|
||||
| | | bytes offset) to test how the MMC card |
|
||||
| | | handles unaligned data transfers. |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 12 | Badly aligned read | same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 13 | Badly aligned multi-block write| same for multi-write |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 14 | Badly aligned multi-block read | same for multi-read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 15 | Proper xfer_size at write | intentionally create a broken transfer by |
|
||||
| | (Start failure) | modifying the MMC request in a way that it |
|
||||
| | | will not perform as expected, e.g. use |
|
||||
| | | MMC_WRITE_BLOCK for a multi-block transfer |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 16 | Proper xfer_size at read | same for read |
|
||||
| | (Start failure) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 17 | Proper xfer_size at write | same for 2 blocks |
|
||||
| | (Midway failure) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 18 | Proper xfer_size at read | same for read |
|
||||
| | (Midway failure) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 19 | Highmem write | use a high memory page |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 20 | Highmem read | same for read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 21 | Multi-block highmem write | same for multi-write |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 22 | Multi-block highmem read | same for mult-read |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 23 | Best-case read performance | Performs 512K sequential read (non sg) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 24 | Best-case write performance | same for write |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 25 | Best-case read performance | Same using sg |
|
||||
| | (Into scattered pages) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 26 | Best-case write performance | same for write |
|
||||
| | (From scattered pages) | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 27 | Single read performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 28 | Single write performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 29 | Single trim performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 30 | Consecutive read performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 31 | Consecutive write performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 32 | Consecutive trim performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 33 | Random read performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 34 | Random write performance | By transfer size |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 35 | Large sequential read | Into scattered pages |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 36 | Large sequential write | From scattered pages |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 37 | Write performance | With blocking req 4k to 4MB |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 38 | Write performance | With non-blocking req 4k to 4MB |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 39 | Read performance | With blocking req 4k to 4MB |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 40 | Read performance | With non-blocking req 4k to 4MB |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 41 | Write performance | Blocking req 1 to 512 sg elems |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 42 | Write performance | Non-blocking req 1 to 512 sg elems |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 43 | Read performance | Blocking req 1 to 512 sg elems |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 44 | Read performance | Non-blocking req 1 to 512 sg elems |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 45 | Reset test | |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 46 | Commands during read | No Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 47 | Commands during write | No Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 48 | Commands during read | Use Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 49 | Commands during write | Use Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 50 | Commands during non-blocking | Read - use Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
| 51 | Commands during non-blocking | Write - use Set Block Count (CMD23) |
|
||||
+------+--------------------------------+---------------------------------------------+
|
||||
|
||||
Test Results
|
||||
============
|
||||
|
||||
The results of the tests are logged in the kernel log. Each test logs the start, end, and result of the test. The possible results are:
|
||||
|
||||
- **OK**: The test completed successfully.
|
||||
- **FAILED**: The test failed.
|
||||
- **UNSUPPORTED (by host)**: The test is unsupported by the host.
|
||||
- **UNSUPPORTED (by card)**: The test is unsupported by the card.
|
||||
- **ERROR**: An error occurred during the test.
|
||||
|
||||
Example Kernel Log Output
|
||||
=========================
|
||||
|
||||
When running a test, you will see log entries similar to the following in the kernel log:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
[ 1234.567890] mmc0: Starting tests of card mmc0:0001...
|
||||
[ 1234.567891] mmc0: Test case 4. Basic read (with data verification)...
|
||||
[ 1234.567892] mmc0: Result: OK
|
||||
[ 1234.567893] mmc0: Tests completed.
|
||||
|
||||
In this example, test case 4 (Basic read with data verification) was executed, and the result was OK.
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Contributions to the `mmc_test` framework are welcome. Please follow the standard Linux kernel contribution guidelines and submit patches to the appropriate maintainers.
|
||||
|
||||
Contact
|
||||
=======
|
||||
|
||||
For more information or to report issues, please contact the MMC subsystem maintainers.
|
@ -19974,6 +19974,13 @@ T: git git://linuxtv.org/media_tree.git
|
||||
F: Documentation/devicetree/bindings/media/allwinner,sun8i-a83t-de2-rotate.yaml
|
||||
F: drivers/media/platform/sunxi/sun8i-rotate/
|
||||
|
||||
RPMB SUBSYSTEM
|
||||
M: Jens Wiklander <jens.wiklander@linaro.org>
|
||||
L: linux-kernel@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/misc/rpmb-core.c
|
||||
F: include/linux/rpmb.h
|
||||
|
||||
RPMSG TTY DRIVER
|
||||
M: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
|
||||
L: linux-remoteproc@vger.kernel.org
|
||||
@ -22580,6 +22587,7 @@ M: Jens Wiklander <jens.wiklander@linaro.org>
|
||||
R: Sumit Garg <sumit.garg@linaro.org>
|
||||
L: op-tee@lists.trustedfirmware.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-class-tee
|
||||
F: Documentation/driver-api/tee.rst
|
||||
F: Documentation/tee/
|
||||
F: Documentation/userspace-api/tee.rst
|
||||
|
@ -104,6 +104,16 @@ config PHANTOM
|
||||
If you choose to build module, its name will be phantom. If unsure,
|
||||
say N here.
|
||||
|
||||
config RPMB
|
||||
tristate "RPMB partition interface"
|
||||
depends on MMC
|
||||
help
|
||||
Unified RPMB unit interface for RPMB capable devices such as eMMC and
|
||||
UFS. Provides interface for in-kernel security controllers to access
|
||||
RPMB unit.
|
||||
|
||||
If unsure, select N.
|
||||
|
||||
config TIFM_CORE
|
||||
tristate "TI Flash Media interface support"
|
||||
depends on PCI
|
||||
|
@ -15,6 +15,7 @@ obj-$(CONFIG_LKDTM) += lkdtm/
|
||||
obj-$(CONFIG_TIFM_CORE) += tifm_core.o
|
||||
obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o
|
||||
obj-$(CONFIG_PHANTOM) += phantom.o
|
||||
obj-$(CONFIG_RPMB) += rpmb-core.o
|
||||
obj-$(CONFIG_QCOM_COINCELL) += qcom-coincell.o
|
||||
obj-$(CONFIG_QCOM_FASTRPC) += fastrpc.o
|
||||
obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o
|
||||
|
231
drivers/misc/rpmb-core.c
Normal file
231
drivers/misc/rpmb-core.c
Normal file
@ -0,0 +1,231 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright(c) 2015 - 2019 Intel Corporation. All rights reserved.
|
||||
* Copyright(c) 2021 - 2024 Linaro Ltd.
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static DEFINE_IDA(rpmb_ida);
|
||||
static DEFINE_MUTEX(rpmb_mutex);
|
||||
|
||||
/**
|
||||
* rpmb_dev_get() - increase rpmb device ref counter
|
||||
* @rdev: rpmb device
|
||||
*/
|
||||
struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev)
|
||||
{
|
||||
if (rdev)
|
||||
get_device(&rdev->dev);
|
||||
return rdev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_dev_get);
|
||||
|
||||
/**
|
||||
* rpmb_dev_put() - decrease rpmb device ref counter
|
||||
* @rdev: rpmb device
|
||||
*/
|
||||
void rpmb_dev_put(struct rpmb_dev *rdev)
|
||||
{
|
||||
if (rdev)
|
||||
put_device(&rdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_dev_put);
|
||||
|
||||
/**
|
||||
* rpmb_route_frames() - route rpmb frames to rpmb device
|
||||
* @rdev: rpmb device
|
||||
* @req: rpmb request frames
|
||||
* @req_len: length of rpmb request frames in bytes
|
||||
* @rsp: rpmb response frames
|
||||
* @rsp_len: length of rpmb response frames in bytes
|
||||
*
|
||||
* Returns: < 0 on failure
|
||||
*/
|
||||
int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req,
|
||||
unsigned int req_len, u8 *rsp, unsigned int rsp_len)
|
||||
{
|
||||
if (!req || !req_len || !rsp || !rsp_len)
|
||||
return -EINVAL;
|
||||
|
||||
return rdev->descr.route_frames(rdev->dev.parent, req, req_len,
|
||||
rsp, rsp_len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_route_frames);
|
||||
|
||||
static void rpmb_dev_release(struct device *dev)
|
||||
{
|
||||
struct rpmb_dev *rdev = to_rpmb_dev(dev);
|
||||
|
||||
mutex_lock(&rpmb_mutex);
|
||||
ida_simple_remove(&rpmb_ida, rdev->id);
|
||||
mutex_unlock(&rpmb_mutex);
|
||||
kfree(rdev->descr.dev_id);
|
||||
kfree(rdev);
|
||||
}
|
||||
|
||||
static struct class rpmb_class = {
|
||||
.name = "rpmb",
|
||||
.dev_release = rpmb_dev_release,
|
||||
};
|
||||
|
||||
/**
|
||||
* rpmb_dev_find_device() - return first matching rpmb device
|
||||
* @start: rpmb device to begin with
|
||||
* @data: data for the match function
|
||||
* @match: the matching function
|
||||
*
|
||||
* Iterate over registered RPMB devices, and call @match() for each passing
|
||||
* it the RPMB device and @data.
|
||||
*
|
||||
* The return value of @match() is checked for each call. If it returns
|
||||
* anything other 0, break and return the found RPMB device.
|
||||
*
|
||||
* It's the callers responsibility to call rpmb_dev_put() on the returned
|
||||
* device, when it's done with it.
|
||||
*
|
||||
* Returns: a matching rpmb device or NULL on failure
|
||||
*/
|
||||
struct rpmb_dev *rpmb_dev_find_device(const void *data,
|
||||
const struct rpmb_dev *start,
|
||||
int (*match)(struct device *dev,
|
||||
const void *data))
|
||||
{
|
||||
struct device *dev;
|
||||
const struct device *start_dev = NULL;
|
||||
|
||||
if (start)
|
||||
start_dev = &start->dev;
|
||||
dev = class_find_device(&rpmb_class, start_dev, data, match);
|
||||
|
||||
return dev ? to_rpmb_dev(dev) : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_dev_find_device);
|
||||
|
||||
int rpmb_interface_register(struct class_interface *intf)
|
||||
{
|
||||
intf->class = &rpmb_class;
|
||||
|
||||
return class_interface_register(intf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_interface_register);
|
||||
|
||||
void rpmb_interface_unregister(struct class_interface *intf)
|
||||
{
|
||||
class_interface_unregister(intf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_interface_unregister);
|
||||
|
||||
/**
|
||||
* rpmb_dev_unregister() - unregister RPMB partition from the RPMB subsystem
|
||||
* @rdev: the rpmb device to unregister
|
||||
*
|
||||
* This function should be called from the release function of the
|
||||
* underlying device used when the RPMB device was registered.
|
||||
*
|
||||
* Returns: < 0 on failure
|
||||
*/
|
||||
int rpmb_dev_unregister(struct rpmb_dev *rdev)
|
||||
{
|
||||
if (!rdev)
|
||||
return -EINVAL;
|
||||
|
||||
device_del(&rdev->dev);
|
||||
|
||||
rpmb_dev_put(rdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_dev_unregister);
|
||||
|
||||
/**
|
||||
* rpmb_dev_register - register RPMB partition with the RPMB subsystem
|
||||
* @dev: storage device of the rpmb device
|
||||
* @descr: RPMB device description
|
||||
*
|
||||
* While registering the RPMB partition extract needed device information
|
||||
* while needed resources are available.
|
||||
*
|
||||
* Returns: a pointer to a 'struct rpmb_dev' or an ERR_PTR on failure
|
||||
*/
|
||||
struct rpmb_dev *rpmb_dev_register(struct device *dev,
|
||||
struct rpmb_descr *descr)
|
||||
{
|
||||
struct rpmb_dev *rdev;
|
||||
int ret;
|
||||
|
||||
if (!dev || !descr || !descr->route_frames || !descr->dev_id ||
|
||||
!descr->dev_id_len)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
rdev = kzalloc(sizeof(*rdev), GFP_KERNEL);
|
||||
if (!rdev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
rdev->descr = *descr;
|
||||
rdev->descr.dev_id = kmemdup(descr->dev_id, descr->dev_id_len,
|
||||
GFP_KERNEL);
|
||||
if (!rdev->descr.dev_id) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_rdev;
|
||||
}
|
||||
|
||||
mutex_lock(&rpmb_mutex);
|
||||
ret = ida_simple_get(&rpmb_ida, 0, 0, GFP_KERNEL);
|
||||
mutex_unlock(&rpmb_mutex);
|
||||
if (ret < 0)
|
||||
goto err_free_dev_id;
|
||||
rdev->id = ret;
|
||||
|
||||
dev_set_name(&rdev->dev, "rpmb%d", rdev->id);
|
||||
rdev->dev.class = &rpmb_class;
|
||||
rdev->dev.parent = dev;
|
||||
|
||||
ret = device_register(&rdev->dev);
|
||||
if (ret) {
|
||||
put_device(&rdev->dev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
dev_dbg(&rdev->dev, "registered device\n");
|
||||
|
||||
return rdev;
|
||||
|
||||
err_free_dev_id:
|
||||
kfree(rdev->descr.dev_id);
|
||||
err_free_rdev:
|
||||
kfree(rdev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rpmb_dev_register);
|
||||
|
||||
static int __init rpmb_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = class_register(&rpmb_class);
|
||||
if (ret) {
|
||||
pr_err("couldn't create class\n");
|
||||
return ret;
|
||||
}
|
||||
ida_init(&rpmb_ida);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rpmb_exit(void)
|
||||
{
|
||||
ida_destroy(&rpmb_ida);
|
||||
class_unregister(&rpmb_class);
|
||||
}
|
||||
|
||||
subsys_initcall(rpmb_init);
|
||||
module_exit(rpmb_exit);
|
||||
|
||||
MODULE_AUTHOR("Jens Wiklander <jens.wiklander@linaro.org>");
|
||||
MODULE_DESCRIPTION("RPMB class");
|
||||
MODULE_LICENSE("GPL");
|
@ -37,6 +37,7 @@ config PWRSEQ_SIMPLE
|
||||
config MMC_BLOCK
|
||||
tristate "MMC block device driver"
|
||||
depends on BLOCK
|
||||
depends on RPMB || !RPMB
|
||||
imply IOSCHED_BFQ
|
||||
default y
|
||||
help
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/string_helpers.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/capability.h>
|
||||
@ -40,6 +41,7 @@
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/rpmb.h>
|
||||
|
||||
#include <linux/mmc/ioctl.h>
|
||||
#include <linux/mmc/card.h>
|
||||
@ -76,6 +78,48 @@ MODULE_ALIAS("mmc:block");
|
||||
#define MMC_EXTRACT_INDEX_FROM_ARG(x) ((x & 0x00FF0000) >> 16)
|
||||
#define MMC_EXTRACT_VALUE_FROM_ARG(x) ((x & 0x0000FF00) >> 8)
|
||||
|
||||
/**
|
||||
* struct rpmb_frame - rpmb frame as defined by eMMC 5.1 (JESD84-B51)
|
||||
*
|
||||
* @stuff : stuff bytes
|
||||
* @key_mac : The authentication key or the message authentication
|
||||
* code (MAC) depending on the request/response type.
|
||||
* The MAC will be delivered in the last (or the only)
|
||||
* block of data.
|
||||
* @data : Data to be written or read by signed access.
|
||||
* @nonce : Random number generated by the host for the requests
|
||||
* and copied to the response by the RPMB engine.
|
||||
* @write_counter: Counter value for the total amount of the successful
|
||||
* authenticated data write requests made by the host.
|
||||
* @addr : Address of the data to be programmed to or read
|
||||
* from the RPMB. Address is the serial number of
|
||||
* the accessed block (half sector 256B).
|
||||
* @block_count : Number of blocks (half sectors, 256B) requested to be
|
||||
* read/programmed.
|
||||
* @result : Includes information about the status of the write counter
|
||||
* (valid, expired) and result of the access made to the RPMB.
|
||||
* @req_resp : Defines the type of request and response to/from the memory.
|
||||
*
|
||||
* The stuff bytes and big-endian properties are modeled to fit to the spec.
|
||||
*/
|
||||
struct rpmb_frame {
|
||||
u8 stuff[196];
|
||||
u8 key_mac[32];
|
||||
u8 data[256];
|
||||
u8 nonce[16];
|
||||
__be32 write_counter;
|
||||
__be16 addr;
|
||||
__be16 block_count;
|
||||
__be16 result;
|
||||
__be16 req_resp;
|
||||
} __packed;
|
||||
|
||||
#define RPMB_PROGRAM_KEY 0x1 /* Program RPMB Authentication Key */
|
||||
#define RPMB_GET_WRITE_COUNTER 0x2 /* Read RPMB write counter */
|
||||
#define RPMB_WRITE_DATA 0x3 /* Write data to RPMB partition */
|
||||
#define RPMB_READ_DATA 0x4 /* Read data from RPMB partition */
|
||||
#define RPMB_RESULT_READ 0x5 /* Read result request (Internal) */
|
||||
|
||||
static DEFINE_MUTEX(block_mutex);
|
||||
|
||||
/*
|
||||
@ -155,6 +199,7 @@ static const struct bus_type mmc_rpmb_bus_type = {
|
||||
* @id: unique device ID number
|
||||
* @part_index: partition index (0 on first)
|
||||
* @md: parent MMC block device
|
||||
* @rdev: registered RPMB device
|
||||
* @node: list item, so we can put this device on a list
|
||||
*/
|
||||
struct mmc_rpmb_data {
|
||||
@ -163,6 +208,7 @@ struct mmc_rpmb_data {
|
||||
int id;
|
||||
unsigned int part_index;
|
||||
struct mmc_blk_data *md;
|
||||
struct rpmb_dev *rdev;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
@ -307,10 +353,10 @@ static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
char *end;
|
||||
struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
|
||||
unsigned long set = simple_strtoul(buf, &end, 0);
|
||||
if (end == buf) {
|
||||
unsigned long set;
|
||||
|
||||
if (kstrtoul(buf, 0, &set)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
@ -2484,7 +2530,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
|
||||
return ERR_PTR(devidx);
|
||||
}
|
||||
|
||||
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
|
||||
md = kzalloc(sizeof(*md), GFP_KERNEL);
|
||||
if (!md) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
@ -2670,7 +2716,6 @@ static int mmc_rpmb_chrdev_open(struct inode *inode, struct file *filp)
|
||||
|
||||
get_device(&rpmb->dev);
|
||||
filp->private_data = rpmb;
|
||||
mmc_blk_get(rpmb->md->disk);
|
||||
|
||||
return nonseekable_open(inode, filp);
|
||||
}
|
||||
@ -2680,7 +2725,6 @@ static int mmc_rpmb_chrdev_release(struct inode *inode, struct file *filp)
|
||||
struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev,
|
||||
struct mmc_rpmb_data, chrdev);
|
||||
|
||||
mmc_blk_put(rpmb->md);
|
||||
put_device(&rpmb->dev);
|
||||
|
||||
return 0;
|
||||
@ -2701,10 +2745,165 @@ static void mmc_blk_rpmb_device_release(struct device *dev)
|
||||
{
|
||||
struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev);
|
||||
|
||||
rpmb_dev_unregister(rpmb->rdev);
|
||||
mmc_blk_put(rpmb->md);
|
||||
ida_free(&mmc_rpmb_ida, rpmb->id);
|
||||
kfree(rpmb);
|
||||
}
|
||||
|
||||
static void free_idata(struct mmc_blk_ioc_data **idata, unsigned int cmd_count)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
for (n = 0; n < cmd_count; n++)
|
||||
kfree(idata[n]);
|
||||
kfree(idata);
|
||||
}
|
||||
|
||||
static struct mmc_blk_ioc_data **alloc_idata(struct mmc_rpmb_data *rpmb,
|
||||
unsigned int cmd_count)
|
||||
{
|
||||
struct mmc_blk_ioc_data **idata;
|
||||
unsigned int n;
|
||||
|
||||
idata = kcalloc(cmd_count, sizeof(*idata), GFP_KERNEL);
|
||||
if (!idata)
|
||||
return NULL;
|
||||
|
||||
for (n = 0; n < cmd_count; n++) {
|
||||
idata[n] = kcalloc(1, sizeof(**idata), GFP_KERNEL);
|
||||
if (!idata[n]) {
|
||||
free_idata(idata, n);
|
||||
return NULL;
|
||||
}
|
||||
idata[n]->rpmb = rpmb;
|
||||
}
|
||||
|
||||
return idata;
|
||||
}
|
||||
|
||||
static void set_idata(struct mmc_blk_ioc_data *idata, u32 opcode,
|
||||
int write_flag, u8 *buf, unsigned int buf_bytes)
|
||||
{
|
||||
/*
|
||||
* The size of an RPMB frame must match what's expected by the
|
||||
* hardware.
|
||||
*/
|
||||
BUILD_BUG_ON(sizeof(struct rpmb_frame) != 512);
|
||||
|
||||
idata->ic.opcode = opcode;
|
||||
idata->ic.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
idata->ic.write_flag = write_flag;
|
||||
idata->ic.blksz = sizeof(struct rpmb_frame);
|
||||
idata->ic.blocks = buf_bytes / idata->ic.blksz;
|
||||
idata->buf = buf;
|
||||
idata->buf_bytes = buf_bytes;
|
||||
}
|
||||
|
||||
static int mmc_route_rpmb_frames(struct device *dev, u8 *req,
|
||||
unsigned int req_len, u8 *resp,
|
||||
unsigned int resp_len)
|
||||
{
|
||||
struct rpmb_frame *frm = (struct rpmb_frame *)req;
|
||||
struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev);
|
||||
struct mmc_blk_data *md = rpmb->md;
|
||||
struct mmc_blk_ioc_data **idata;
|
||||
struct mmc_queue_req *mq_rq;
|
||||
unsigned int cmd_count;
|
||||
struct request *rq;
|
||||
u16 req_type;
|
||||
bool write;
|
||||
int ret;
|
||||
|
||||
if (IS_ERR(md->queue.card))
|
||||
return PTR_ERR(md->queue.card);
|
||||
|
||||
if (req_len < sizeof(*frm))
|
||||
return -EINVAL;
|
||||
|
||||
req_type = be16_to_cpu(frm->req_resp);
|
||||
switch (req_type) {
|
||||
case RPMB_PROGRAM_KEY:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
return -EINVAL;
|
||||
write = true;
|
||||
break;
|
||||
case RPMB_GET_WRITE_COUNTER:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
return -EINVAL;
|
||||
write = false;
|
||||
break;
|
||||
case RPMB_WRITE_DATA:
|
||||
if (req_len % sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
return -EINVAL;
|
||||
write = true;
|
||||
break;
|
||||
case RPMB_READ_DATA:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len % sizeof(struct rpmb_frame))
|
||||
return -EINVAL;
|
||||
write = false;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (write)
|
||||
cmd_count = 3;
|
||||
else
|
||||
cmd_count = 2;
|
||||
|
||||
idata = alloc_idata(rpmb, cmd_count);
|
||||
if (!idata)
|
||||
return -ENOMEM;
|
||||
|
||||
if (write) {
|
||||
struct rpmb_frame *frm = (struct rpmb_frame *)resp;
|
||||
|
||||
/* Send write request frame(s) */
|
||||
set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK,
|
||||
1 | MMC_CMD23_ARG_REL_WR, req, req_len);
|
||||
|
||||
/* Send result request frame */
|
||||
memset(frm, 0, sizeof(*frm));
|
||||
frm->req_resp = cpu_to_be16(RPMB_RESULT_READ);
|
||||
set_idata(idata[1], MMC_WRITE_MULTIPLE_BLOCK, 1, resp,
|
||||
resp_len);
|
||||
|
||||
/* Read response frame */
|
||||
set_idata(idata[2], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len);
|
||||
} else {
|
||||
/* Send write request frame(s) */
|
||||
set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK, 1, req, req_len);
|
||||
|
||||
/* Read response frame */
|
||||
set_idata(idata[1], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len);
|
||||
}
|
||||
|
||||
rq = blk_mq_alloc_request(md->queue.queue, REQ_OP_DRV_OUT, 0);
|
||||
if (IS_ERR(rq)) {
|
||||
ret = PTR_ERR(rq);
|
||||
goto out;
|
||||
}
|
||||
|
||||
mq_rq = req_to_mmc_queue_req(rq);
|
||||
mq_rq->drv_op = MMC_DRV_OP_IOCTL_RPMB;
|
||||
mq_rq->drv_op_result = -EIO;
|
||||
mq_rq->drv_op_data = idata;
|
||||
mq_rq->ioc_count = cmd_count;
|
||||
blk_execute_rq(rq, false);
|
||||
ret = req_to_mmc_queue_req(rq)->drv_op_result;
|
||||
|
||||
blk_mq_free_request(rq);
|
||||
|
||||
out:
|
||||
free_idata(idata, cmd_count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_blk_alloc_rpmb_part(struct mmc_card *card,
|
||||
struct mmc_blk_data *md,
|
||||
unsigned int part_index,
|
||||
@ -2739,6 +2938,7 @@ static int mmc_blk_alloc_rpmb_part(struct mmc_card *card,
|
||||
rpmb->dev.release = mmc_blk_rpmb_device_release;
|
||||
device_initialize(&rpmb->dev);
|
||||
dev_set_drvdata(&rpmb->dev, rpmb);
|
||||
mmc_blk_get(md->disk);
|
||||
rpmb->md = md;
|
||||
|
||||
cdev_init(&rpmb->chrdev, &mmc_rpmb_fileops);
|
||||
@ -3000,6 +3200,42 @@ static void mmc_blk_remove_debugfs(struct mmc_card *card,
|
||||
|
||||
#endif /* CONFIG_DEBUG_FS */
|
||||
|
||||
static void mmc_blk_rpmb_add(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md = dev_get_drvdata(&card->dev);
|
||||
struct mmc_rpmb_data *rpmb;
|
||||
struct rpmb_dev *rdev;
|
||||
unsigned int n;
|
||||
u32 cid[4];
|
||||
struct rpmb_descr descr = {
|
||||
.type = RPMB_TYPE_EMMC,
|
||||
.route_frames = mmc_route_rpmb_frames,
|
||||
.reliable_wr_count = card->ext_csd.enhanced_rpmb_supported ?
|
||||
2 : 32,
|
||||
.capacity = card->ext_csd.raw_rpmb_size_mult,
|
||||
.dev_id = (void *)cid,
|
||||
.dev_id_len = sizeof(cid),
|
||||
};
|
||||
|
||||
/*
|
||||
* Provice CID as an octet array. The CID needs to be interpreted
|
||||
* when used as input to derive the RPMB key since some fields
|
||||
* will change due to firmware updates.
|
||||
*/
|
||||
for (n = 0; n < 4; n++)
|
||||
cid[n] = be32_to_cpu((__force __be32)card->raw_cid[n]);
|
||||
|
||||
list_for_each_entry(rpmb, &md->rpmbs, node) {
|
||||
rdev = rpmb_dev_register(&rpmb->dev, &descr);
|
||||
if (IS_ERR(rdev)) {
|
||||
pr_warn("%s: could not register RPMB device\n",
|
||||
dev_name(&rpmb->dev));
|
||||
continue;
|
||||
}
|
||||
rpmb->rdev = rdev;
|
||||
}
|
||||
}
|
||||
|
||||
static int mmc_blk_probe(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_blk_data *md;
|
||||
@ -3045,6 +3281,8 @@ static int mmc_blk_probe(struct mmc_card *card)
|
||||
pm_runtime_enable(&card->dev);
|
||||
}
|
||||
|
||||
mmc_blk_rpmb_add(card);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
|
@ -51,20 +51,6 @@ static const unsigned int taac_mant[] = {
|
||||
35, 40, 45, 50, 55, 60, 70, 80,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Given the decoded CSD structure, decode the raw CID to our CID structure.
|
||||
*/
|
||||
@ -85,36 +71,36 @@ static int mmc_decode_cid(struct mmc_card *card)
|
||||
switch (card->csd.mmca_vsn) {
|
||||
case 0: /* MMC v1.0 - v1.2 */
|
||||
case 1: /* MMC v1.4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 104, 24);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 24);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
card->cid.manfid = unstuff_bits(resp, 104, 24);
|
||||
card->cid.prod_name[0] = unstuff_bits(resp, 96, 8);
|
||||
card->cid.prod_name[1] = unstuff_bits(resp, 88, 8);
|
||||
card->cid.prod_name[2] = unstuff_bits(resp, 80, 8);
|
||||
card->cid.prod_name[3] = unstuff_bits(resp, 72, 8);
|
||||
card->cid.prod_name[4] = unstuff_bits(resp, 64, 8);
|
||||
card->cid.prod_name[5] = unstuff_bits(resp, 56, 8);
|
||||
card->cid.prod_name[6] = unstuff_bits(resp, 48, 8);
|
||||
card->cid.hwrev = unstuff_bits(resp, 44, 4);
|
||||
card->cid.fwrev = unstuff_bits(resp, 40, 4);
|
||||
card->cid.serial = unstuff_bits(resp, 16, 24);
|
||||
card->cid.month = unstuff_bits(resp, 12, 4);
|
||||
card->cid.year = unstuff_bits(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
case 2: /* MMC v2.0 - v2.2 */
|
||||
case 3: /* MMC v3.1 - v3.3 */
|
||||
case 4: /* MMC v4 */
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
|
||||
card->cid.prv = UNSTUFF_BITS(resp, 48, 8);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 12, 4);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
|
||||
card->cid.manfid = unstuff_bits(resp, 120, 8);
|
||||
card->cid.oemid = unstuff_bits(resp, 104, 16);
|
||||
card->cid.prod_name[0] = unstuff_bits(resp, 96, 8);
|
||||
card->cid.prod_name[1] = unstuff_bits(resp, 88, 8);
|
||||
card->cid.prod_name[2] = unstuff_bits(resp, 80, 8);
|
||||
card->cid.prod_name[3] = unstuff_bits(resp, 72, 8);
|
||||
card->cid.prod_name[4] = unstuff_bits(resp, 64, 8);
|
||||
card->cid.prod_name[5] = unstuff_bits(resp, 56, 8);
|
||||
card->cid.prv = unstuff_bits(resp, 48, 8);
|
||||
card->cid.serial = unstuff_bits(resp, 16, 32);
|
||||
card->cid.month = unstuff_bits(resp, 12, 4);
|
||||
card->cid.year = unstuff_bits(resp, 8, 4) + 1997;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -161,43 +147,43 @@ static int mmc_decode_csd(struct mmc_card *card)
|
||||
* v1.2 has extra information in bits 15, 11 and 10.
|
||||
* We also support eMMC v4.4 & v4.41.
|
||||
*/
|
||||
csd->structure = UNSTUFF_BITS(resp, 126, 2);
|
||||
csd->structure = unstuff_bits(resp, 126, 2);
|
||||
if (csd->structure == 0) {
|
||||
pr_err("%s: unrecognised CSD structure version %d\n",
|
||||
mmc_hostname(card->host), csd->structure);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4);
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
csd->mmca_vsn = unstuff_bits(resp, 122, 4);
|
||||
m = unstuff_bits(resp, 115, 4);
|
||||
e = unstuff_bits(resp, 112, 3);
|
||||
csd->taac_ns = (taac_exp[e] * taac_mant[m] + 9) / 10;
|
||||
csd->taac_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
csd->taac_clks = unstuff_bits(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
m = unstuff_bits(resp, 99, 4);
|
||||
e = unstuff_bits(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
csd->cmdclass = unstuff_bits(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
e = unstuff_bits(resp, 47, 3);
|
||||
m = unstuff_bits(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->dsr_imp = UNSTUFF_BITS(resp, 76, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
csd->read_blkbits = unstuff_bits(resp, 80, 4);
|
||||
csd->read_partial = unstuff_bits(resp, 79, 1);
|
||||
csd->write_misalign = unstuff_bits(resp, 78, 1);
|
||||
csd->read_misalign = unstuff_bits(resp, 77, 1);
|
||||
csd->dsr_imp = unstuff_bits(resp, 76, 1);
|
||||
csd->r2w_factor = unstuff_bits(resp, 26, 3);
|
||||
csd->write_blkbits = unstuff_bits(resp, 22, 4);
|
||||
csd->write_partial = unstuff_bits(resp, 21, 1);
|
||||
|
||||
if (csd->write_blkbits >= 9) {
|
||||
a = UNSTUFF_BITS(resp, 42, 5);
|
||||
b = UNSTUFF_BITS(resp, 37, 5);
|
||||
a = unstuff_bits(resp, 42, 5);
|
||||
b = unstuff_bits(resp, 37, 5);
|
||||
csd->erase_size = (a + 1) * (b + 1);
|
||||
csd->erase_size <<= csd->write_blkbits - 9;
|
||||
csd->wp_grp_size = UNSTUFF_BITS(resp, 32, 5);
|
||||
csd->wp_grp_size = unstuff_bits(resp, 32, 5);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -56,5 +56,19 @@ int mmc_cmdq_enable(struct mmc_card *card);
|
||||
int mmc_cmdq_disable(struct mmc_card *card);
|
||||
int mmc_sanitize(struct mmc_card *card, unsigned int timeout_ms);
|
||||
|
||||
static inline u32 unstuff_bits(const u32 *resp, int start, int size)
|
||||
{
|
||||
const int __size = size;
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1;
|
||||
const int __off = 3 - (start / 32);
|
||||
const int __shft = start & 31;
|
||||
u32 __res = resp[__off] >> __shft;
|
||||
|
||||
if (__size + __shft > 32)
|
||||
__res |= resp[__off - 1] << ((32 - __shft) % 32);
|
||||
|
||||
return __res & __mask;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -255,7 +255,9 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
|
||||
|
||||
if (IS_ERR(mmc->supply.vmmc)) {
|
||||
if (PTR_ERR(mmc->supply.vmmc) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
return dev_err_probe(dev, -EPROBE_DEFER,
|
||||
"vmmc regulator not available\n");
|
||||
|
||||
dev_dbg(dev, "No vmmc regulator found\n");
|
||||
} else {
|
||||
ret = mmc_regulator_get_ocrmask(mmc->supply.vmmc);
|
||||
@ -267,7 +269,9 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
|
||||
|
||||
if (IS_ERR(mmc->supply.vqmmc)) {
|
||||
if (PTR_ERR(mmc->supply.vqmmc) == -EPROBE_DEFER)
|
||||
return -EPROBE_DEFER;
|
||||
return dev_err_probe(dev, -EPROBE_DEFER,
|
||||
"vqmmc regulator not available\n");
|
||||
|
||||
dev_dbg(dev, "No vqmmc regulator found\n");
|
||||
}
|
||||
|
||||
|
@ -56,20 +56,6 @@ static const unsigned int sd_au_size[] = {
|
||||
SZ_16M / 512, (SZ_16M + SZ_8M) / 512, SZ_32M / 512, SZ_64M / 512,
|
||||
};
|
||||
|
||||
#define UNSTUFF_BITS(resp,start,size) \
|
||||
({ \
|
||||
const int __size = size; \
|
||||
const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
|
||||
const int __off = 3 - ((start) / 32); \
|
||||
const int __shft = (start) & 31; \
|
||||
u32 __res; \
|
||||
\
|
||||
__res = resp[__off] >> __shft; \
|
||||
if (__size + __shft > 32) \
|
||||
__res |= resp[__off-1] << ((32 - __shft) % 32); \
|
||||
__res & __mask; \
|
||||
})
|
||||
|
||||
#define SD_POWEROFF_NOTIFY_TIMEOUT_MS 1000
|
||||
#define SD_WRITE_EXTR_SINGLE_TIMEOUT_MS 1000
|
||||
|
||||
@ -95,18 +81,18 @@ void mmc_decode_cid(struct mmc_card *card)
|
||||
* SD doesn't currently have a version field so we will
|
||||
* have to assume we can parse this.
|
||||
*/
|
||||
card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
|
||||
card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
|
||||
card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
|
||||
card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
|
||||
card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
|
||||
card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
|
||||
card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
|
||||
card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4);
|
||||
card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4);
|
||||
card->cid.serial = UNSTUFF_BITS(resp, 24, 32);
|
||||
card->cid.year = UNSTUFF_BITS(resp, 12, 8);
|
||||
card->cid.month = UNSTUFF_BITS(resp, 8, 4);
|
||||
card->cid.manfid = unstuff_bits(resp, 120, 8);
|
||||
card->cid.oemid = unstuff_bits(resp, 104, 16);
|
||||
card->cid.prod_name[0] = unstuff_bits(resp, 96, 8);
|
||||
card->cid.prod_name[1] = unstuff_bits(resp, 88, 8);
|
||||
card->cid.prod_name[2] = unstuff_bits(resp, 80, 8);
|
||||
card->cid.prod_name[3] = unstuff_bits(resp, 72, 8);
|
||||
card->cid.prod_name[4] = unstuff_bits(resp, 64, 8);
|
||||
card->cid.hwrev = unstuff_bits(resp, 60, 4);
|
||||
card->cid.fwrev = unstuff_bits(resp, 56, 4);
|
||||
card->cid.serial = unstuff_bits(resp, 24, 32);
|
||||
card->cid.year = unstuff_bits(resp, 12, 8);
|
||||
card->cid.month = unstuff_bits(resp, 8, 4);
|
||||
|
||||
card->cid.year += 2000; /* SD cards year offset */
|
||||
}
|
||||
@ -120,41 +106,41 @@ static int mmc_decode_csd(struct mmc_card *card)
|
||||
unsigned int e, m, csd_struct;
|
||||
u32 *resp = card->raw_csd;
|
||||
|
||||
csd_struct = UNSTUFF_BITS(resp, 126, 2);
|
||||
csd_struct = unstuff_bits(resp, 126, 2);
|
||||
|
||||
switch (csd_struct) {
|
||||
case 0:
|
||||
m = UNSTUFF_BITS(resp, 115, 4);
|
||||
e = UNSTUFF_BITS(resp, 112, 3);
|
||||
m = unstuff_bits(resp, 115, 4);
|
||||
e = unstuff_bits(resp, 112, 3);
|
||||
csd->taac_ns = (taac_exp[e] * taac_mant[m] + 9) / 10;
|
||||
csd->taac_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
|
||||
csd->taac_clks = unstuff_bits(resp, 104, 8) * 100;
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
m = unstuff_bits(resp, 99, 4);
|
||||
e = unstuff_bits(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
csd->cmdclass = unstuff_bits(resp, 84, 12);
|
||||
|
||||
e = UNSTUFF_BITS(resp, 47, 3);
|
||||
m = UNSTUFF_BITS(resp, 62, 12);
|
||||
e = unstuff_bits(resp, 47, 3);
|
||||
m = unstuff_bits(resp, 62, 12);
|
||||
csd->capacity = (1 + m) << (e + 2);
|
||||
|
||||
csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
|
||||
csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
|
||||
csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
|
||||
csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
|
||||
csd->dsr_imp = UNSTUFF_BITS(resp, 76, 1);
|
||||
csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
|
||||
csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
|
||||
csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
|
||||
csd->read_blkbits = unstuff_bits(resp, 80, 4);
|
||||
csd->read_partial = unstuff_bits(resp, 79, 1);
|
||||
csd->write_misalign = unstuff_bits(resp, 78, 1);
|
||||
csd->read_misalign = unstuff_bits(resp, 77, 1);
|
||||
csd->dsr_imp = unstuff_bits(resp, 76, 1);
|
||||
csd->r2w_factor = unstuff_bits(resp, 26, 3);
|
||||
csd->write_blkbits = unstuff_bits(resp, 22, 4);
|
||||
csd->write_partial = unstuff_bits(resp, 21, 1);
|
||||
|
||||
if (UNSTUFF_BITS(resp, 46, 1)) {
|
||||
if (unstuff_bits(resp, 46, 1)) {
|
||||
csd->erase_size = 1;
|
||||
} else if (csd->write_blkbits >= 9) {
|
||||
csd->erase_size = UNSTUFF_BITS(resp, 39, 7) + 1;
|
||||
csd->erase_size = unstuff_bits(resp, 39, 7) + 1;
|
||||
csd->erase_size <<= csd->write_blkbits - 9;
|
||||
}
|
||||
|
||||
if (UNSTUFF_BITS(resp, 13, 1))
|
||||
if (unstuff_bits(resp, 13, 1))
|
||||
mmc_card_set_readonly(card);
|
||||
break;
|
||||
case 1:
|
||||
@ -169,17 +155,17 @@ static int mmc_decode_csd(struct mmc_card *card)
|
||||
csd->taac_ns = 0; /* Unused */
|
||||
csd->taac_clks = 0; /* Unused */
|
||||
|
||||
m = UNSTUFF_BITS(resp, 99, 4);
|
||||
e = UNSTUFF_BITS(resp, 96, 3);
|
||||
m = unstuff_bits(resp, 99, 4);
|
||||
e = unstuff_bits(resp, 96, 3);
|
||||
csd->max_dtr = tran_exp[e] * tran_mant[m];
|
||||
csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
|
||||
csd->c_size = UNSTUFF_BITS(resp, 48, 22);
|
||||
csd->cmdclass = unstuff_bits(resp, 84, 12);
|
||||
csd->c_size = unstuff_bits(resp, 48, 22);
|
||||
|
||||
/* SDXC cards have a minimum C_SIZE of 0x00FFFF */
|
||||
if (csd->c_size >= 0xFFFF)
|
||||
mmc_card_set_ext_capacity(card);
|
||||
|
||||
m = UNSTUFF_BITS(resp, 48, 22);
|
||||
m = unstuff_bits(resp, 48, 22);
|
||||
csd->capacity = (1 + m) << 10;
|
||||
|
||||
csd->read_blkbits = 9;
|
||||
@ -191,7 +177,7 @@ static int mmc_decode_csd(struct mmc_card *card)
|
||||
csd->write_partial = 0;
|
||||
csd->erase_size = 1;
|
||||
|
||||
if (UNSTUFF_BITS(resp, 13, 1))
|
||||
if (unstuff_bits(resp, 13, 1))
|
||||
mmc_card_set_readonly(card);
|
||||
break;
|
||||
default:
|
||||
@ -217,33 +203,33 @@ static int mmc_decode_scr(struct mmc_card *card)
|
||||
resp[3] = card->raw_scr[1];
|
||||
resp[2] = card->raw_scr[0];
|
||||
|
||||
scr_struct = UNSTUFF_BITS(resp, 60, 4);
|
||||
scr_struct = unstuff_bits(resp, 60, 4);
|
||||
if (scr_struct != 0) {
|
||||
pr_err("%s: unrecognised SCR structure version %d\n",
|
||||
mmc_hostname(card->host), scr_struct);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4);
|
||||
scr->bus_widths = UNSTUFF_BITS(resp, 48, 4);
|
||||
scr->sda_vsn = unstuff_bits(resp, 56, 4);
|
||||
scr->bus_widths = unstuff_bits(resp, 48, 4);
|
||||
if (scr->sda_vsn == SCR_SPEC_VER_2)
|
||||
/* Check if Physical Layer Spec v3.0 is supported */
|
||||
scr->sda_spec3 = UNSTUFF_BITS(resp, 47, 1);
|
||||
scr->sda_spec3 = unstuff_bits(resp, 47, 1);
|
||||
|
||||
if (scr->sda_spec3) {
|
||||
scr->sda_spec4 = UNSTUFF_BITS(resp, 42, 1);
|
||||
scr->sda_specx = UNSTUFF_BITS(resp, 38, 4);
|
||||
scr->sda_spec4 = unstuff_bits(resp, 42, 1);
|
||||
scr->sda_specx = unstuff_bits(resp, 38, 4);
|
||||
}
|
||||
|
||||
if (UNSTUFF_BITS(resp, 55, 1))
|
||||
if (unstuff_bits(resp, 55, 1))
|
||||
card->erased_byte = 0xFF;
|
||||
else
|
||||
card->erased_byte = 0x0;
|
||||
|
||||
if (scr->sda_spec4)
|
||||
scr->cmds = UNSTUFF_BITS(resp, 32, 4);
|
||||
scr->cmds = unstuff_bits(resp, 32, 4);
|
||||
else if (scr->sda_spec3)
|
||||
scr->cmds = UNSTUFF_BITS(resp, 32, 2);
|
||||
scr->cmds = unstuff_bits(resp, 32, 2);
|
||||
|
||||
/* SD Spec says: any SD Card shall set at least bits 0 and 2 */
|
||||
if (!(scr->bus_widths & SD_SCR_BUS_WIDTH_1) ||
|
||||
@ -289,17 +275,17 @@ static int mmc_read_ssr(struct mmc_card *card)
|
||||
kfree(raw_ssr);
|
||||
|
||||
/*
|
||||
* UNSTUFF_BITS only works with four u32s so we have to offset the
|
||||
* unstuff_bits only works with four u32s so we have to offset the
|
||||
* bitfield positions accordingly.
|
||||
*/
|
||||
au = UNSTUFF_BITS(card->raw_ssr, 428 - 384, 4);
|
||||
au = unstuff_bits(card->raw_ssr, 428 - 384, 4);
|
||||
if (au) {
|
||||
if (au <= 9 || card->scr.sda_spec3) {
|
||||
card->ssr.au = sd_au_size[au];
|
||||
es = UNSTUFF_BITS(card->raw_ssr, 408 - 384, 16);
|
||||
et = UNSTUFF_BITS(card->raw_ssr, 402 - 384, 6);
|
||||
es = unstuff_bits(card->raw_ssr, 408 - 384, 16);
|
||||
et = unstuff_bits(card->raw_ssr, 402 - 384, 6);
|
||||
if (es && et) {
|
||||
eo = UNSTUFF_BITS(card->raw_ssr, 400 - 384, 2);
|
||||
eo = unstuff_bits(card->raw_ssr, 400 - 384, 2);
|
||||
card->ssr.erase_timeout = (et * 1000) / es;
|
||||
card->ssr.erase_offset = eo * 1000;
|
||||
}
|
||||
@ -313,7 +299,7 @@ static int mmc_read_ssr(struct mmc_card *card)
|
||||
* starting SD5.1 discard is supported if DISCARD_SUPPORT (b313) is set
|
||||
*/
|
||||
resp[3] = card->raw_ssr[6];
|
||||
discard_support = UNSTUFF_BITS(resp, 313 - 288, 1);
|
||||
discard_support = unstuff_bits(resp, 313 - 288, 1);
|
||||
card->erase_arg = (card->scr.sda_specx && discard_support) ?
|
||||
SD_DISCARD_ARG : SD_ERASE_ARG;
|
||||
|
||||
@ -346,7 +332,7 @@ static int mmc_read_switch(struct mmc_card *card)
|
||||
* The argument does not matter, as the support bits do not
|
||||
* change with the arguments.
|
||||
*/
|
||||
err = mmc_sd_switch(card, 0, 0, 0, status);
|
||||
err = mmc_sd_switch(card, SD_SWITCH_CHECK, 0, 0, status);
|
||||
if (err) {
|
||||
/*
|
||||
* If the host or the card can't do the switch,
|
||||
@ -402,7 +388,8 @@ int mmc_sd_switch_hs(struct mmc_card *card)
|
||||
if (!status)
|
||||
return -ENOMEM;
|
||||
|
||||
err = mmc_sd_switch(card, 1, 0, HIGH_SPEED_BUS_SPEED, status);
|
||||
err = mmc_sd_switch(card, SD_SWITCH_SET, 0,
|
||||
HIGH_SPEED_BUS_SPEED, status);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
@ -434,7 +421,8 @@ static int sd_select_driver_type(struct mmc_card *card, u8 *status)
|
||||
card_drv_type, &drv_type);
|
||||
|
||||
if (drive_strength) {
|
||||
err = mmc_sd_switch(card, 1, 2, drive_strength, status);
|
||||
err = mmc_sd_switch(card, SD_SWITCH_SET, 2,
|
||||
drive_strength, status);
|
||||
if (err)
|
||||
return err;
|
||||
if ((status[15] & 0xF) != drive_strength) {
|
||||
@ -514,7 +502,7 @@ static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status)
|
||||
return 0;
|
||||
}
|
||||
|
||||
err = mmc_sd_switch(card, 1, 0, card->sd_bus_speed, status);
|
||||
err = mmc_sd_switch(card, SD_SWITCH_SET, 0, card->sd_bus_speed, status);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@ -605,7 +593,8 @@ static int sd_set_current_limit(struct mmc_card *card, u8 *status)
|
||||
current_limit = SD_SET_CURRENT_LIMIT_200;
|
||||
|
||||
if (current_limit != SD_SET_CURRENT_NO_CHANGE) {
|
||||
err = mmc_sd_switch(card, 1, 3, current_limit, status);
|
||||
err = mmc_sd_switch(card, SD_SWITCH_SET, 3,
|
||||
current_limit, status);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
@ -336,14 +336,13 @@ int mmc_app_send_scr(struct mmc_card *card)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group,
|
||||
int mmc_sd_switch(struct mmc_card *card, bool mode, int group,
|
||||
u8 value, u8 *resp)
|
||||
{
|
||||
u32 cmd_args;
|
||||
|
||||
/* NOTE: caller guarantees resp is heap-allocated */
|
||||
|
||||
mode = !!mode;
|
||||
value &= 0xF;
|
||||
cmd_args = mode << 31 | 0x00FFFFFF;
|
||||
cmd_args &= ~(0xF << (group * 4));
|
||||
|
@ -252,6 +252,18 @@ config MMC_SDHCI_OF_SPARX5
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_SDHCI_OF_MA35D1
|
||||
tristate "SDHCI OF support for the MA35D1 SDHCI controller"
|
||||
depends on ARCH_MA35 || COMPILE_TEST
|
||||
depends on MMC_SDHCI_PLTFM
|
||||
help
|
||||
This selects the MA35D1 Secure Digital Host Controller Interface.
|
||||
The controller supports SD/MMC/SDIO devices.
|
||||
|
||||
If you have a controller with this interface, say Y or M here.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_SDHCI_CADENCE
|
||||
tristate "SDHCI support for the Cadence SD/SDIO/eMMC controller"
|
||||
depends on MMC_SDHCI_PLTFM
|
||||
|
@ -88,6 +88,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
|
||||
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
|
||||
obj-$(CONFIG_MMC_SDHCI_OF_DWCMSHC) += sdhci-of-dwcmshc.o
|
||||
obj-$(CONFIG_MMC_SDHCI_OF_SPARX5) += sdhci-of-sparx5.o
|
||||
obj-$(CONFIG_MMC_SDHCI_OF_MA35D1) += sdhci-of-ma35d1.o
|
||||
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
|
||||
obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o
|
||||
obj-$(CONFIG_MMC_SDHCI_NPCM) += sdhci-npcm.o
|
||||
|
@ -33,6 +33,11 @@ struct cqhci_slot {
|
||||
#define CQHCI_HOST_OTHER BIT(4)
|
||||
};
|
||||
|
||||
static bool cqhci_halted(struct cqhci_host *cq_host)
|
||||
{
|
||||
return cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT;
|
||||
}
|
||||
|
||||
static inline u8 *get_desc(struct cqhci_host *cq_host, u8 tag)
|
||||
{
|
||||
return cq_host->desc_base + (tag * cq_host->slot_sz);
|
||||
@ -282,7 +287,7 @@ static void __cqhci_enable(struct cqhci_host *cq_host)
|
||||
|
||||
cqhci_writel(cq_host, cqcfg, CQHCI_CFG);
|
||||
|
||||
if (cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT)
|
||||
if (cqhci_halted(cq_host))
|
||||
cqhci_writel(cq_host, 0, CQHCI_CTL);
|
||||
|
||||
mmc->cqe_on = true;
|
||||
@ -617,7 +622,7 @@ static int cqhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
cqhci_writel(cq_host, 0, CQHCI_CTL);
|
||||
mmc->cqe_on = true;
|
||||
pr_debug("%s: cqhci: CQE on\n", mmc_hostname(mmc));
|
||||
if (cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT) {
|
||||
if (cqhci_halted(cq_host)) {
|
||||
pr_err("%s: cqhci: CQE failed to exit halt state\n",
|
||||
mmc_hostname(mmc));
|
||||
}
|
||||
@ -953,11 +958,6 @@ static bool cqhci_clear_all_tasks(struct mmc_host *mmc, unsigned int timeout)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool cqhci_halted(struct cqhci_host *cq_host)
|
||||
{
|
||||
return cqhci_readl(cq_host, CQHCI_CTL) & CQHCI_HALT;
|
||||
}
|
||||
|
||||
static bool cqhci_halt(struct mmc_host *mmc, unsigned int timeout)
|
||||
{
|
||||
struct cqhci_host *cq_host = mmc->cqe_private;
|
||||
|
@ -16,6 +16,16 @@
|
||||
#include "dw_mmc-pltfm.h"
|
||||
|
||||
#define RK3288_CLKGEN_DIV 2
|
||||
#define SDMMC_TIMING_CON0 0x130
|
||||
#define SDMMC_TIMING_CON1 0x134
|
||||
#define ROCKCHIP_MMC_DELAY_SEL BIT(10)
|
||||
#define ROCKCHIP_MMC_DEGREE_MASK 0x3
|
||||
#define ROCKCHIP_MMC_DEGREE_OFFSET 1
|
||||
#define ROCKCHIP_MMC_DELAYNUM_OFFSET 2
|
||||
#define ROCKCHIP_MMC_DELAYNUM_MASK (0xff << ROCKCHIP_MMC_DELAYNUM_OFFSET)
|
||||
#define ROCKCHIP_MMC_DELAY_ELEMENT_PSEC 60
|
||||
#define HIWORD_UPDATE(val, mask, shift) \
|
||||
((val) << (shift) | (mask) << ((shift) + 16))
|
||||
|
||||
static const unsigned int freqs[] = { 100000, 200000, 300000, 400000 };
|
||||
|
||||
@ -24,8 +34,143 @@ struct dw_mci_rockchip_priv_data {
|
||||
struct clk *sample_clk;
|
||||
int default_sample_phase;
|
||||
int num_phases;
|
||||
bool internal_phase;
|
||||
};
|
||||
|
||||
/*
|
||||
* Each fine delay is between 44ps-77ps. Assume each fine delay is 60ps to
|
||||
* simplify calculations. So 45degs could be anywhere between 33deg and 57.8deg.
|
||||
*/
|
||||
static int rockchip_mmc_get_internal_phase(struct dw_mci *host, bool sample)
|
||||
{
|
||||
unsigned long rate = clk_get_rate(host->ciu_clk);
|
||||
u32 raw_value;
|
||||
u16 degrees;
|
||||
u32 delay_num = 0;
|
||||
|
||||
/* Constant signal, no measurable phase shift */
|
||||
if (!rate)
|
||||
return 0;
|
||||
|
||||
if (sample)
|
||||
raw_value = mci_readl(host, TIMING_CON1);
|
||||
else
|
||||
raw_value = mci_readl(host, TIMING_CON0);
|
||||
|
||||
raw_value >>= ROCKCHIP_MMC_DEGREE_OFFSET;
|
||||
degrees = (raw_value & ROCKCHIP_MMC_DEGREE_MASK) * 90;
|
||||
|
||||
if (raw_value & ROCKCHIP_MMC_DELAY_SEL) {
|
||||
/* degrees/delaynum * 1000000 */
|
||||
unsigned long factor = (ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10) *
|
||||
36 * (rate / 10000);
|
||||
|
||||
delay_num = (raw_value & ROCKCHIP_MMC_DELAYNUM_MASK);
|
||||
delay_num >>= ROCKCHIP_MMC_DELAYNUM_OFFSET;
|
||||
degrees += DIV_ROUND_CLOSEST(delay_num * factor, 1000000);
|
||||
}
|
||||
|
||||
return degrees % 360;
|
||||
}
|
||||
|
||||
static int rockchip_mmc_get_phase(struct dw_mci *host, bool sample)
|
||||
{
|
||||
struct dw_mci_rockchip_priv_data *priv = host->priv;
|
||||
struct clk *clock = sample ? priv->sample_clk : priv->drv_clk;
|
||||
|
||||
if (priv->internal_phase)
|
||||
return rockchip_mmc_get_internal_phase(host, sample);
|
||||
else
|
||||
return clk_get_phase(clock);
|
||||
}
|
||||
|
||||
static int rockchip_mmc_set_internal_phase(struct dw_mci *host, bool sample, int degrees)
|
||||
{
|
||||
unsigned long rate = clk_get_rate(host->ciu_clk);
|
||||
u8 nineties, remainder;
|
||||
u8 delay_num;
|
||||
u32 raw_value;
|
||||
u32 delay;
|
||||
|
||||
/*
|
||||
* The below calculation is based on the output clock from
|
||||
* MMC host to the card, which expects the phase clock inherits
|
||||
* the clock rate from its parent, namely the output clock
|
||||
* provider of MMC host. However, things may go wrong if
|
||||
* (1) It is orphan.
|
||||
* (2) It is assigned to the wrong parent.
|
||||
*
|
||||
* This check help debug the case (1), which seems to be the
|
||||
* most likely problem we often face and which makes it difficult
|
||||
* for people to debug unstable mmc tuning results.
|
||||
*/
|
||||
if (!rate) {
|
||||
dev_err(host->dev, "%s: invalid clk rate\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
nineties = degrees / 90;
|
||||
remainder = (degrees % 90);
|
||||
|
||||
/*
|
||||
* Due to the inexact nature of the "fine" delay, we might
|
||||
* actually go non-monotonic. We don't go _too_ monotonic
|
||||
* though, so we should be OK. Here are options of how we may
|
||||
* work:
|
||||
*
|
||||
* Ideally we end up with:
|
||||
* 1.0, 2.0, ..., 69.0, 70.0, ..., 89.0, 90.0
|
||||
*
|
||||
* On one extreme (if delay is actually 44ps):
|
||||
* .73, 1.5, ..., 50.6, 51.3, ..., 65.3, 90.0
|
||||
* The other (if delay is actually 77ps):
|
||||
* 1.3, 2.6, ..., 88.6. 89.8, ..., 114.0, 90
|
||||
*
|
||||
* It's possible we might make a delay that is up to 25
|
||||
* degrees off from what we think we're making. That's OK
|
||||
* though because we should be REALLY far from any bad range.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert to delay; do a little extra work to make sure we
|
||||
* don't overflow 32-bit / 64-bit numbers.
|
||||
*/
|
||||
delay = 10000000; /* PSECS_PER_SEC / 10000 / 10 */
|
||||
delay *= remainder;
|
||||
delay = DIV_ROUND_CLOSEST(delay,
|
||||
(rate / 1000) * 36 *
|
||||
(ROCKCHIP_MMC_DELAY_ELEMENT_PSEC / 10));
|
||||
|
||||
delay_num = (u8) min_t(u32, delay, 255);
|
||||
|
||||
raw_value = delay_num ? ROCKCHIP_MMC_DELAY_SEL : 0;
|
||||
raw_value |= delay_num << ROCKCHIP_MMC_DELAYNUM_OFFSET;
|
||||
raw_value |= nineties;
|
||||
|
||||
if (sample)
|
||||
mci_writel(host, TIMING_CON1, HIWORD_UPDATE(raw_value, 0x07ff, 1));
|
||||
else
|
||||
mci_writel(host, TIMING_CON0, HIWORD_UPDATE(raw_value, 0x07ff, 1));
|
||||
|
||||
dev_dbg(host->dev, "set %s_phase(%d) delay_nums=%u actual_degrees=%d\n",
|
||||
sample ? "sample" : "drv", degrees, delay_num,
|
||||
rockchip_mmc_get_phase(host, sample)
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_mmc_set_phase(struct dw_mci *host, bool sample, int degrees)
|
||||
{
|
||||
struct dw_mci_rockchip_priv_data *priv = host->priv;
|
||||
struct clk *clock = sample ? priv->sample_clk : priv->drv_clk;
|
||||
|
||||
if (priv->internal_phase)
|
||||
return rockchip_mmc_set_internal_phase(host, sample, degrees);
|
||||
else
|
||||
return clk_set_phase(clock, degrees);
|
||||
}
|
||||
|
||||
static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
||||
{
|
||||
struct dw_mci_rockchip_priv_data *priv = host->priv;
|
||||
@ -64,7 +209,7 @@ static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
||||
|
||||
/* Make sure we use phases which we can enumerate with */
|
||||
if (!IS_ERR(priv->sample_clk) && ios->timing <= MMC_TIMING_SD_HS)
|
||||
clk_set_phase(priv->sample_clk, priv->default_sample_phase);
|
||||
rockchip_mmc_set_phase(host, true, priv->default_sample_phase);
|
||||
|
||||
/*
|
||||
* Set the drive phase offset based on speed mode to achieve hold times.
|
||||
@ -127,7 +272,7 @@ static void dw_mci_rk3288_set_ios(struct dw_mci *host, struct mmc_ios *ios)
|
||||
break;
|
||||
}
|
||||
|
||||
clk_set_phase(priv->drv_clk, phase);
|
||||
rockchip_mmc_set_phase(host, false, phase);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,6 +296,7 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
|
||||
int longest_range_len = -1;
|
||||
int longest_range = -1;
|
||||
int middle_phase;
|
||||
int phase;
|
||||
|
||||
if (IS_ERR(priv->sample_clk)) {
|
||||
dev_err(host->dev, "Tuning clock (sample_clk) not defined.\n");
|
||||
@ -164,8 +310,10 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
|
||||
|
||||
/* Try each phase and extract good ranges */
|
||||
for (i = 0; i < priv->num_phases; ) {
|
||||
clk_set_phase(priv->sample_clk,
|
||||
TUNING_ITERATION_TO_PHASE(i, priv->num_phases));
|
||||
rockchip_mmc_set_phase(host, true,
|
||||
TUNING_ITERATION_TO_PHASE(
|
||||
i,
|
||||
priv->num_phases));
|
||||
|
||||
v = !mmc_send_tuning(mmc, opcode, NULL);
|
||||
|
||||
@ -211,7 +359,8 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
|
||||
}
|
||||
|
||||
if (ranges[0].start == 0 && ranges[0].end == priv->num_phases - 1) {
|
||||
clk_set_phase(priv->sample_clk, priv->default_sample_phase);
|
||||
rockchip_mmc_set_phase(host, true, priv->default_sample_phase);
|
||||
|
||||
dev_info(host->dev, "All phases work, using default phase %d.",
|
||||
priv->default_sample_phase);
|
||||
goto free;
|
||||
@ -248,19 +397,17 @@ static int dw_mci_rk3288_execute_tuning(struct dw_mci_slot *slot, u32 opcode)
|
||||
|
||||
middle_phase = ranges[longest_range].start + longest_range_len / 2;
|
||||
middle_phase %= priv->num_phases;
|
||||
dev_info(host->dev, "Successfully tuned phase to %d\n",
|
||||
TUNING_ITERATION_TO_PHASE(middle_phase, priv->num_phases));
|
||||
phase = TUNING_ITERATION_TO_PHASE(middle_phase, priv->num_phases);
|
||||
dev_info(host->dev, "Successfully tuned phase to %d\n", phase);
|
||||
|
||||
clk_set_phase(priv->sample_clk,
|
||||
TUNING_ITERATION_TO_PHASE(middle_phase,
|
||||
priv->num_phases));
|
||||
rockchip_mmc_set_phase(host, true, phase);
|
||||
|
||||
free:
|
||||
kfree(ranges);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dw_mci_rk3288_parse_dt(struct dw_mci *host)
|
||||
static int dw_mci_common_parse_dt(struct dw_mci *host)
|
||||
{
|
||||
struct device_node *np = host->dev->of_node;
|
||||
struct dw_mci_rockchip_priv_data *priv;
|
||||
@ -277,6 +424,22 @@ static int dw_mci_rk3288_parse_dt(struct dw_mci *host)
|
||||
&priv->default_sample_phase))
|
||||
priv->default_sample_phase = 0;
|
||||
|
||||
host->priv = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_mci_rk3288_parse_dt(struct dw_mci *host)
|
||||
{
|
||||
struct dw_mci_rockchip_priv_data *priv;
|
||||
int err;
|
||||
|
||||
err = dw_mci_common_parse_dt(host);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
priv = host->priv;
|
||||
|
||||
priv->drv_clk = devm_clk_get(host->dev, "ciu-drive");
|
||||
if (IS_ERR(priv->drv_clk))
|
||||
dev_dbg(host->dev, "ciu-drive not available\n");
|
||||
@ -285,7 +448,21 @@ static int dw_mci_rk3288_parse_dt(struct dw_mci *host)
|
||||
if (IS_ERR(priv->sample_clk))
|
||||
dev_dbg(host->dev, "ciu-sample not available\n");
|
||||
|
||||
host->priv = priv;
|
||||
priv->internal_phase = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_mci_rk3576_parse_dt(struct dw_mci *host)
|
||||
{
|
||||
struct dw_mci_rockchip_priv_data *priv;
|
||||
int err = dw_mci_common_parse_dt(host);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
priv = host->priv;
|
||||
|
||||
priv->internal_phase = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -331,11 +508,21 @@ static const struct dw_mci_drv_data rk3288_drv_data = {
|
||||
.init = dw_mci_rockchip_init,
|
||||
};
|
||||
|
||||
static const struct dw_mci_drv_data rk3576_drv_data = {
|
||||
.common_caps = MMC_CAP_CMD23,
|
||||
.set_ios = dw_mci_rk3288_set_ios,
|
||||
.execute_tuning = dw_mci_rk3288_execute_tuning,
|
||||
.parse_dt = dw_mci_rk3576_parse_dt,
|
||||
.init = dw_mci_rockchip_init,
|
||||
};
|
||||
|
||||
static const struct of_device_id dw_mci_rockchip_match[] = {
|
||||
{ .compatible = "rockchip,rk2928-dw-mshc",
|
||||
.data = &rk2928_drv_data },
|
||||
{ .compatible = "rockchip,rk3288-dw-mshc",
|
||||
.data = &rk3288_drv_data },
|
||||
{ .compatible = "rockchip,rk3576-dw-mshc",
|
||||
.data = &rk3576_drv_data },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dw_mci_rockchip_match);
|
||||
|
@ -795,14 +795,13 @@ static void msdc_unprepare_data(struct msdc_host *host, struct mmc_data *data)
|
||||
static u64 msdc_timeout_cal(struct msdc_host *host, u64 ns, u64 clks)
|
||||
{
|
||||
struct mmc_host *mmc = mmc_from_priv(host);
|
||||
u64 timeout, clk_ns;
|
||||
u32 mode = 0;
|
||||
u64 timeout;
|
||||
u32 clk_ns, mode = 0;
|
||||
|
||||
if (mmc->actual_clock == 0) {
|
||||
timeout = 0;
|
||||
} else {
|
||||
clk_ns = 1000000000ULL;
|
||||
do_div(clk_ns, mmc->actual_clock);
|
||||
clk_ns = 1000000000U / mmc->actual_clock;
|
||||
timeout = ns + clk_ns - 1;
|
||||
do_div(timeout, clk_ns);
|
||||
timeout += clks;
|
||||
@ -831,7 +830,7 @@ static void msdc_set_timeout(struct msdc_host *host, u64 ns, u64 clks)
|
||||
|
||||
timeout = msdc_timeout_cal(host, ns, clks);
|
||||
sdr_set_field(host->base + SDC_CFG, SDC_CFG_DTOC,
|
||||
(u32)(timeout > 255 ? 255 : timeout));
|
||||
min_t(u32, timeout, 255));
|
||||
}
|
||||
|
||||
static void msdc_set_busy_timeout(struct msdc_host *host, u64 ns, u64 clks)
|
||||
@ -840,7 +839,7 @@ static void msdc_set_busy_timeout(struct msdc_host *host, u64 ns, u64 clks)
|
||||
|
||||
timeout = msdc_timeout_cal(host, ns, clks);
|
||||
sdr_set_field(host->base + SDC_CFG, SDC_CFG_WRDTOC,
|
||||
(u32)(timeout > 8191 ? 8191 : timeout));
|
||||
min_t(u32, timeout, 8191));
|
||||
}
|
||||
|
||||
static void msdc_gate_clock(struct msdc_host *host)
|
||||
|
@ -285,6 +285,7 @@ static const struct of_device_id renesas_sdhi_internal_dmac_of_match[] = {
|
||||
{ .compatible = "renesas,sdhi-r8a77990", .data = &of_r8a77990_compatible, },
|
||||
{ .compatible = "renesas,sdhi-r8a77995", .data = &of_rcar_gen3_nohs400_compatible, },
|
||||
{ .compatible = "renesas,sdhi-r9a09g011", .data = &of_rzg2l_compatible, },
|
||||
{ .compatible = "renesas,sdhi-r9a09g057", .data = &of_rzg2l_compatible, },
|
||||
{ .compatible = "renesas,rzg2l-sdhi", .data = &of_rzg2l_compatible, },
|
||||
{ .compatible = "renesas,rcar-gen3-sdhi", .data = &of_rcar_gen3_compatible, },
|
||||
{ .compatible = "renesas,rcar-gen4-sdhi", .data = &of_rcar_gen3_compatible, },
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
@ -108,7 +109,6 @@
|
||||
#define DLL_LOCK_WO_TMOUT(x) \
|
||||
((((x) & DWCMSHC_EMMC_DLL_LOCKED) == DWCMSHC_EMMC_DLL_LOCKED) && \
|
||||
(((x) & DWCMSHC_EMMC_DLL_TIMEOUT) == 0))
|
||||
#define RK35xx_MAX_CLKS 3
|
||||
|
||||
/* PHY register area pointer */
|
||||
#define DWC_MSHC_PTR_PHY_R 0x300
|
||||
@ -116,10 +116,13 @@
|
||||
/* PHY general configuration */
|
||||
#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00)
|
||||
#define PHY_CNFG_RSTN_DEASSERT 0x1 /* Deassert PHY reset */
|
||||
#define PHY_CNFG_PHY_PWRGOOD_MASK BIT_MASK(1) /* bit [1] */
|
||||
#define PHY_CNFG_PAD_SP_MASK GENMASK(19, 16) /* bits [19:16] */
|
||||
#define PHY_CNFG_PAD_SP 0x0c /* PMOS TX drive strength */
|
||||
#define PHY_CNFG_PAD_SP_SG2042 0x09 /* PMOS TX drive strength for SG2042 */
|
||||
#define PHY_CNFG_PAD_SN_MASK GENMASK(23, 20) /* bits [23:20] */
|
||||
#define PHY_CNFG_PAD_SN 0x0c /* NMOS TX drive strength */
|
||||
#define PHY_CNFG_PAD_SN_SG2042 0x08 /* NMOS TX drive strength for SG2042 */
|
||||
|
||||
/* PHY command/response pad settings */
|
||||
#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04)
|
||||
@ -148,9 +151,11 @@
|
||||
#define PHY_PAD_TXSLEW_CTRL_P 0x3 /* Slew control for P-Type pad TX */
|
||||
#define PHY_PAD_TXSLEW_CTRL_N_MASK GENMASK(12, 9) /* bits [12:9] */
|
||||
#define PHY_PAD_TXSLEW_CTRL_N 0x3 /* Slew control for N-Type pad TX */
|
||||
#define PHY_PAD_TXSLEW_CTRL_N_SG2042 0x2 /* Slew control for N-Type pad TX for SG2042 */
|
||||
|
||||
/* PHY CLK delay line settings */
|
||||
#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d)
|
||||
#define PHY_SDCLKDL_CNFG_EXTDLY_EN BIT(0)
|
||||
#define PHY_SDCLKDL_CNFG_UPDATE BIT(4) /* set before writing to SDCLKDL_DC */
|
||||
|
||||
/* PHY CLK delay line delay code */
|
||||
@ -159,10 +164,14 @@
|
||||
#define PHY_SDCLKDL_DC_DEFAULT 0x32 /* default delay code */
|
||||
#define PHY_SDCLKDL_DC_HS400 0x18 /* delay code for HS400 mode */
|
||||
|
||||
#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20)
|
||||
#define PHY_SMPLDL_CNFG_BYPASS_EN BIT(1)
|
||||
|
||||
/* PHY drift_cclk_rx delay line configuration setting */
|
||||
#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21)
|
||||
#define PHY_ATDL_CNFG_INPSEL_MASK GENMASK(3, 2) /* bits [3:2] */
|
||||
#define PHY_ATDL_CNFG_INPSEL 0x3 /* delay line input source */
|
||||
#define PHY_ATDL_CNFG_INPSEL_SG2042 0x2 /* delay line input source for SG2042 */
|
||||
|
||||
/* PHY DLL control settings */
|
||||
#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24)
|
||||
@ -193,29 +202,69 @@
|
||||
SDHCI_TRNS_BLK_CNT_EN | \
|
||||
SDHCI_TRNS_DMA)
|
||||
|
||||
/* SMC call for BlueField-3 eMMC RST_N */
|
||||
#define BLUEFIELD_SMC_SET_EMMC_RST_N 0x82000007
|
||||
|
||||
enum dwcmshc_rk_type {
|
||||
DWCMSHC_RK3568,
|
||||
DWCMSHC_RK3588,
|
||||
};
|
||||
|
||||
struct rk35xx_priv {
|
||||
/* Rockchip specified optional clocks */
|
||||
struct clk_bulk_data rockchip_clks[RK35xx_MAX_CLKS];
|
||||
struct reset_control *reset;
|
||||
enum dwcmshc_rk_type devtype;
|
||||
u8 txclk_tapnum;
|
||||
};
|
||||
|
||||
#define DWCMSHC_MAX_OTHER_CLKS 3
|
||||
|
||||
struct dwcmshc_priv {
|
||||
struct clk *bus_clk;
|
||||
int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */
|
||||
int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */
|
||||
|
||||
int num_other_clks;
|
||||
struct clk_bulk_data other_clks[DWCMSHC_MAX_OTHER_CLKS];
|
||||
|
||||
void *priv; /* pointer to SoC private stuff */
|
||||
u16 delay_line;
|
||||
u16 flags;
|
||||
};
|
||||
|
||||
struct dwcmshc_pltfm_data {
|
||||
const struct sdhci_pltfm_data pdata;
|
||||
int (*init)(struct device *dev, struct sdhci_host *host, struct dwcmshc_priv *dwc_priv);
|
||||
void (*postinit)(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv);
|
||||
};
|
||||
|
||||
static int dwcmshc_get_enable_other_clks(struct device *dev,
|
||||
struct dwcmshc_priv *priv,
|
||||
int num_clks,
|
||||
const char * const clk_ids[])
|
||||
{
|
||||
int err;
|
||||
|
||||
if (num_clks > DWCMSHC_MAX_OTHER_CLKS)
|
||||
return -EINVAL;
|
||||
|
||||
for (int i = 0; i < num_clks; i++)
|
||||
priv->other_clks[i].id = clk_ids[i];
|
||||
|
||||
err = devm_clk_bulk_get_optional(dev, num_clks, priv->other_clks);
|
||||
if (err) {
|
||||
dev_err(dev, "failed to get clocks %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_bulk_prepare_enable(num_clks, priv->other_clks);
|
||||
if (err)
|
||||
dev_err(dev, "failed to enable clocks %d\n", err);
|
||||
|
||||
priv->num_other_clks = num_clks;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* If DMA addr spans 128MB boundary, we split the DMA transfer into two
|
||||
* so that each DMA transfer doesn't exceed the boundary.
|
||||
@ -681,6 +730,63 @@ static void rk35xx_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
sdhci_reset(host, mask);
|
||||
}
|
||||
|
||||
static int dwcmshc_rk35xx_init(struct device *dev, struct sdhci_host *host,
|
||||
struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
static const char * const clk_ids[] = {"axi", "block", "timer"};
|
||||
struct rk35xx_priv *priv;
|
||||
int err;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(struct rk35xx_priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_device_is_compatible(dev->of_node, "rockchip,rk3588-dwcmshc"))
|
||||
priv->devtype = DWCMSHC_RK3588;
|
||||
else
|
||||
priv->devtype = DWCMSHC_RK3568;
|
||||
|
||||
priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc));
|
||||
if (IS_ERR(priv->reset)) {
|
||||
err = PTR_ERR(priv->reset);
|
||||
dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv,
|
||||
ARRAY_SIZE(clk_ids), clk_ids);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum",
|
||||
&priv->txclk_tapnum))
|
||||
priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT;
|
||||
|
||||
/* Disable cmd conflict check */
|
||||
sdhci_writel(host, 0x0, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
|
||||
/* Reset previous settings */
|
||||
sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK);
|
||||
sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_STRBIN);
|
||||
|
||||
dwc_priv->priv = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
/*
|
||||
* Don't support highspeed bus mode with low clk speed as we
|
||||
* cannot use DLL for this condition.
|
||||
*/
|
||||
if (host->mmc->f_max <= 52000000) {
|
||||
dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n",
|
||||
host->mmc->f_max);
|
||||
host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400);
|
||||
host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR);
|
||||
}
|
||||
}
|
||||
|
||||
static int th1520_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
@ -755,6 +861,35 @@ static void th1520_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
}
|
||||
}
|
||||
|
||||
static int th1520_init(struct device *dev,
|
||||
struct sdhci_host *host,
|
||||
struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
dwc_priv->delay_line = PHY_SDCLKDL_DC_DEFAULT;
|
||||
|
||||
if (device_property_read_bool(dev, "mmc-ddr-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs200-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs400-1_8v"))
|
||||
dwc_priv->flags |= FLAG_IO_FIXED_1V8;
|
||||
else
|
||||
dwc_priv->flags &= ~FLAG_IO_FIXED_1V8;
|
||||
|
||||
/*
|
||||
* start_signal_voltage_switch() will try 3.3V first
|
||||
* then 1.8V. Use SDHCI_SIGNALING_180 rather than
|
||||
* SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V
|
||||
* in sdhci_start_signal_voltage_switch().
|
||||
*/
|
||||
if (dwc_priv->flags & FLAG_IO_FIXED_1V8) {
|
||||
host->flags &= ~SDHCI_SIGNALING_330;
|
||||
host->flags |= SDHCI_SIGNALING_180;
|
||||
}
|
||||
|
||||
sdhci_enable_v4_mode(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cv18xx_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
@ -891,6 +1026,85 @@ static int cv18xx_sdhci_execute_tuning(struct sdhci_host *host, u32 opcode)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void sg2042_sdhci_phy_init(struct sdhci_host *host)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Asset phy reset & set tx drive strength */
|
||||
val = sdhci_readl(host, PHY_CNFG_R);
|
||||
val &= ~PHY_CNFG_RSTN_DEASSERT;
|
||||
val |= FIELD_PREP(PHY_CNFG_PHY_PWRGOOD_MASK, 1);
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SP_MASK, PHY_CNFG_PAD_SP_SG2042);
|
||||
val |= FIELD_PREP(PHY_CNFG_PAD_SN_MASK, PHY_CNFG_PAD_SN_SG2042);
|
||||
sdhci_writel(host, val, PHY_CNFG_R);
|
||||
|
||||
/* Configure phy pads */
|
||||
val = PHY_PAD_RXSEL_3V3;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLUP);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
|
||||
sdhci_writew(host, val, PHY_CMDPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_DATAPAD_CNFG_R);
|
||||
sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R);
|
||||
|
||||
val = PHY_PAD_RXSEL_3V3;
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
|
||||
sdhci_writew(host, val, PHY_CLKPAD_CNFG_R);
|
||||
|
||||
val = PHY_PAD_RXSEL_3V3;
|
||||
val |= FIELD_PREP(PHY_PAD_WEAKPULL_MASK, PHY_PAD_WEAKPULL_PULLDOWN);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_P_MASK, PHY_PAD_TXSLEW_CTRL_P);
|
||||
val |= FIELD_PREP(PHY_PAD_TXSLEW_CTRL_N_MASK, PHY_PAD_TXSLEW_CTRL_N_SG2042);
|
||||
sdhci_writew(host, val, PHY_STBPAD_CNFG_R);
|
||||
|
||||
/* Configure delay line */
|
||||
/* Enable fixed delay */
|
||||
sdhci_writeb(host, PHY_SDCLKDL_CNFG_EXTDLY_EN, PHY_SDCLKDL_CNFG_R);
|
||||
/*
|
||||
* Set delay line.
|
||||
* Its recommended that bit UPDATE_DC[4] is 1 when SDCLKDL_DC is being written.
|
||||
* Ensure UPDATE_DC[4] is '0' when not updating code.
|
||||
*/
|
||||
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
||||
val |= PHY_SDCLKDL_CNFG_UPDATE;
|
||||
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
||||
/* Add 10 * 70ps = 0.7ns for output delay */
|
||||
sdhci_writeb(host, 10, PHY_SDCLKDL_DC_R);
|
||||
val = sdhci_readb(host, PHY_SDCLKDL_CNFG_R);
|
||||
val &= ~(PHY_SDCLKDL_CNFG_UPDATE);
|
||||
sdhci_writeb(host, val, PHY_SDCLKDL_CNFG_R);
|
||||
|
||||
/* Set SMPLDL_CNFG, Bypass */
|
||||
sdhci_writeb(host, PHY_SMPLDL_CNFG_BYPASS_EN, PHY_SMPLDL_CNFG_R);
|
||||
|
||||
/* Set ATDL_CNFG, tuning clk not use for init */
|
||||
val = FIELD_PREP(PHY_ATDL_CNFG_INPSEL_MASK, PHY_ATDL_CNFG_INPSEL_SG2042);
|
||||
sdhci_writeb(host, val, PHY_ATDL_CNFG_R);
|
||||
|
||||
/* Deasset phy reset */
|
||||
val = sdhci_readl(host, PHY_CNFG_R);
|
||||
val |= PHY_CNFG_RSTN_DEASSERT;
|
||||
sdhci_writel(host, val, PHY_CNFG_R);
|
||||
}
|
||||
|
||||
static void sg2042_sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
sdhci_reset(host, mask);
|
||||
|
||||
if (mask & SDHCI_RESET_ALL)
|
||||
sg2042_sdhci_phy_init(host);
|
||||
}
|
||||
|
||||
static int sg2042_init(struct device *dev, struct sdhci_host *host,
|
||||
struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
static const char * const clk_ids[] = {"timer"};
|
||||
|
||||
return dwcmshc_get_enable_other_clks(mmc_dev(host->mmc), dwc_priv,
|
||||
ARRAY_SIZE(clk_ids), clk_ids);
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
@ -901,6 +1115,29 @@ static const struct sdhci_ops sdhci_dwcmshc_ops = {
|
||||
.irq = dwcmshc_cqe_irq_handler,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static void dwcmshc_bf3_hw_reset(struct sdhci_host *host)
|
||||
{
|
||||
struct arm_smccc_res res = { 0 };
|
||||
|
||||
arm_smccc_smc(BLUEFIELD_SMC_SET_EMMC_RST_N, 0, 0, 0, 0, 0, 0, 0, &res);
|
||||
|
||||
if (res.a0)
|
||||
pr_err("%s: RST_N failed.\n", mmc_hostname(host->mmc));
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_bf3_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_uhs_signaling = dwcmshc_set_uhs_signaling,
|
||||
.get_max_clock = dwcmshc_get_max_clock,
|
||||
.reset = sdhci_reset,
|
||||
.adma_write_desc = dwcmshc_adma_write_desc,
|
||||
.irq = dwcmshc_cqe_irq_handler,
|
||||
.hw_reset = dwcmshc_bf3_hw_reset,
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct sdhci_ops sdhci_dwcmshc_rk35xx_ops = {
|
||||
.set_clock = dwcmshc_rk3568_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
@ -932,39 +1169,71 @@ static const struct sdhci_ops sdhci_dwcmshc_cv18xx_ops = {
|
||||
.platform_execute_tuning = cv18xx_sdhci_execute_tuning,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
|
||||
static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = {
|
||||
.set_clock = sdhci_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_uhs_signaling = dwcmshc_set_uhs_signaling,
|
||||
.get_max_clock = dwcmshc_get_max_clock,
|
||||
.reset = sg2042_sdhci_reset,
|
||||
.adma_write_desc = dwcmshc_adma_write_desc,
|
||||
.platform_execute_tuning = th1520_execute_tuning,
|
||||
};
|
||||
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
},
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_bf3_pdata = {
|
||||
.ops = &sdhci_dwcmshc_ops,
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_bf3_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
||||
SDHCI_QUIRK2_ACMD23_BROKEN,
|
||||
},
|
||||
};
|
||||
#endif
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_rk35xx_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
|
||||
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
|
||||
SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
|
||||
},
|
||||
.init = dwcmshc_rk35xx_init,
|
||||
.postinit = dwcmshc_rk35xx_postinit,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_th1520_pdata = {
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_th1520_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_th1520_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
},
|
||||
.init = th1520_init,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_dwcmshc_cv18xx_pdata = {
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_cv18xx_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_cv18xx_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct dwcmshc_pltfm_data sdhci_dwcmshc_sg2042_pdata = {
|
||||
.pdata = {
|
||||
.ops = &sdhci_dwcmshc_sg2042_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
|
||||
},
|
||||
.init = sg2042_init,
|
||||
};
|
||||
|
||||
static const struct cqhci_host_ops dwcmshc_cqhci_ops = {
|
||||
@ -1034,61 +1303,6 @@ dsbl_cqe_caps:
|
||||
host->mmc->caps2 &= ~(MMC_CAP2_CQE | MMC_CAP2_CQE_DCMD);
|
||||
}
|
||||
|
||||
static int dwcmshc_rk35xx_init(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
int err;
|
||||
struct rk35xx_priv *priv = dwc_priv->priv;
|
||||
|
||||
priv->reset = devm_reset_control_array_get_optional_exclusive(mmc_dev(host->mmc));
|
||||
if (IS_ERR(priv->reset)) {
|
||||
err = PTR_ERR(priv->reset);
|
||||
dev_err(mmc_dev(host->mmc), "failed to get reset control %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->rockchip_clks[0].id = "axi";
|
||||
priv->rockchip_clks[1].id = "block";
|
||||
priv->rockchip_clks[2].id = "timer";
|
||||
err = devm_clk_bulk_get_optional(mmc_dev(host->mmc), RK35xx_MAX_CLKS,
|
||||
priv->rockchip_clks);
|
||||
if (err) {
|
||||
dev_err(mmc_dev(host->mmc), "failed to get clocks %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = clk_bulk_prepare_enable(RK35xx_MAX_CLKS, priv->rockchip_clks);
|
||||
if (err) {
|
||||
dev_err(mmc_dev(host->mmc), "failed to enable clocks %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (of_property_read_u8(mmc_dev(host->mmc)->of_node, "rockchip,txclk-tapnum",
|
||||
&priv->txclk_tapnum))
|
||||
priv->txclk_tapnum = DLL_TXCLK_TAPNUM_DEFAULT;
|
||||
|
||||
/* Disable cmd conflict check */
|
||||
sdhci_writel(host, 0x0, dwc_priv->vendor_specific_area1 + DWCMSHC_HOST_CTRL3);
|
||||
/* Reset previous settings */
|
||||
sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_TXCLK);
|
||||
sdhci_writel(host, 0, DWCMSHC_EMMC_DLL_STRBIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dwcmshc_rk35xx_postinit(struct sdhci_host *host, struct dwcmshc_priv *dwc_priv)
|
||||
{
|
||||
/*
|
||||
* Don't support highspeed bus mode with low clk speed as we
|
||||
* cannot use DLL for this condition.
|
||||
*/
|
||||
if (host->mmc->f_max <= 52000000) {
|
||||
dev_info(mmc_dev(host->mmc), "Disabling HS200/HS400, frequency too low (%d)\n",
|
||||
host->mmc->f_max);
|
||||
host->mmc->caps2 &= ~(MMC_CAP2_HS200 | MMC_CAP2_HS400);
|
||||
host->mmc->caps &= ~(MMC_CAP_3_3V_DDR | MMC_CAP_1_8V_DDR);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
|
||||
{
|
||||
.compatible = "rockchip,rk3588-dwcmshc",
|
||||
@ -1114,6 +1328,10 @@ static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
|
||||
.compatible = "thead,th1520-dwcmshc",
|
||||
.data = &sdhci_dwcmshc_th1520_pdata,
|
||||
},
|
||||
{
|
||||
.compatible = "sophgo,sg2042-dwcmshc",
|
||||
.data = &sdhci_dwcmshc_sg2042_pdata,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids);
|
||||
@ -1135,8 +1353,7 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
||||
struct sdhci_pltfm_host *pltfm_host;
|
||||
struct sdhci_host *host;
|
||||
struct dwcmshc_priv *priv;
|
||||
struct rk35xx_priv *rk_priv = NULL;
|
||||
const struct sdhci_pltfm_data *pltfm_data;
|
||||
const struct dwcmshc_pltfm_data *pltfm_data;
|
||||
int err;
|
||||
u32 extra, caps;
|
||||
|
||||
@ -1146,7 +1363,7 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
host = sdhci_pltfm_init(pdev, pltfm_data,
|
||||
host = sdhci_pltfm_init(pdev, &pltfm_data->pdata,
|
||||
sizeof(struct dwcmshc_priv));
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
@ -1191,49 +1408,12 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
||||
host->mmc_host_ops.hs400_enhanced_strobe = dwcmshc_hs400_enhanced_strobe;
|
||||
host->mmc_host_ops.execute_tuning = dwcmshc_execute_tuning;
|
||||
|
||||
if (pltfm_data == &sdhci_dwcmshc_rk35xx_pdata) {
|
||||
rk_priv = devm_kzalloc(&pdev->dev, sizeof(struct rk35xx_priv), GFP_KERNEL);
|
||||
if (!rk_priv) {
|
||||
err = -ENOMEM;
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "rockchip,rk3588-dwcmshc"))
|
||||
rk_priv->devtype = DWCMSHC_RK3588;
|
||||
else
|
||||
rk_priv->devtype = DWCMSHC_RK3568;
|
||||
|
||||
priv->priv = rk_priv;
|
||||
|
||||
err = dwcmshc_rk35xx_init(host, priv);
|
||||
if (pltfm_data->init) {
|
||||
err = pltfm_data->init(&pdev->dev, host, priv);
|
||||
if (err)
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
if (pltfm_data == &sdhci_dwcmshc_th1520_pdata) {
|
||||
priv->delay_line = PHY_SDCLKDL_DC_DEFAULT;
|
||||
|
||||
if (device_property_read_bool(dev, "mmc-ddr-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs200-1_8v") ||
|
||||
device_property_read_bool(dev, "mmc-hs400-1_8v"))
|
||||
priv->flags |= FLAG_IO_FIXED_1V8;
|
||||
else
|
||||
priv->flags &= ~FLAG_IO_FIXED_1V8;
|
||||
|
||||
/*
|
||||
* start_signal_voltage_switch() will try 3.3V first
|
||||
* then 1.8V. Use SDHCI_SIGNALING_180 rather than
|
||||
* SDHCI_SIGNALING_330 to avoid setting voltage to 3.3V
|
||||
* in sdhci_start_signal_voltage_switch().
|
||||
*/
|
||||
if (priv->flags & FLAG_IO_FIXED_1V8) {
|
||||
host->flags &= ~SDHCI_SIGNALING_330;
|
||||
host->flags |= SDHCI_SIGNALING_180;
|
||||
}
|
||||
|
||||
sdhci_enable_v4_mode(host);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
if (pltfm_data == &sdhci_dwcmshc_bf3_pdata)
|
||||
sdhci_enable_v4_mode(host);
|
||||
@ -1261,8 +1441,8 @@ static int dwcmshc_probe(struct platform_device *pdev)
|
||||
dwcmshc_cqhci_init(host, pdev);
|
||||
}
|
||||
|
||||
if (rk_priv)
|
||||
dwcmshc_rk35xx_postinit(host, priv);
|
||||
if (pltfm_data->postinit)
|
||||
pltfm_data->postinit(host, priv);
|
||||
|
||||
err = __sdhci_add_host(host);
|
||||
if (err)
|
||||
@ -1280,9 +1460,7 @@ err_rpm:
|
||||
err_clk:
|
||||
clk_disable_unprepare(pltfm_host->clk);
|
||||
clk_disable_unprepare(priv->bus_clk);
|
||||
if (rk_priv)
|
||||
clk_bulk_disable_unprepare(RK35xx_MAX_CLKS,
|
||||
rk_priv->rockchip_clks);
|
||||
clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks);
|
||||
free_pltfm:
|
||||
sdhci_pltfm_free(pdev);
|
||||
return err;
|
||||
@ -1304,7 +1482,6 @@ static void dwcmshc_remove(struct platform_device *pdev)
|
||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
struct rk35xx_priv *rk_priv = priv->priv;
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
@ -1316,9 +1493,7 @@ static void dwcmshc_remove(struct platform_device *pdev)
|
||||
|
||||
clk_disable_unprepare(pltfm_host->clk);
|
||||
clk_disable_unprepare(priv->bus_clk);
|
||||
if (rk_priv)
|
||||
clk_bulk_disable_unprepare(RK35xx_MAX_CLKS,
|
||||
rk_priv->rockchip_clks);
|
||||
clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks);
|
||||
sdhci_pltfm_free(pdev);
|
||||
}
|
||||
|
||||
@ -1328,7 +1503,6 @@ static int dwcmshc_suspend(struct device *dev)
|
||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
struct rk35xx_priv *rk_priv = priv->priv;
|
||||
int ret;
|
||||
|
||||
pm_runtime_resume(dev);
|
||||
@ -1347,9 +1521,7 @@ static int dwcmshc_suspend(struct device *dev)
|
||||
if (!IS_ERR(priv->bus_clk))
|
||||
clk_disable_unprepare(priv->bus_clk);
|
||||
|
||||
if (rk_priv)
|
||||
clk_bulk_disable_unprepare(RK35xx_MAX_CLKS,
|
||||
rk_priv->rockchip_clks);
|
||||
clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1359,7 +1531,6 @@ static int dwcmshc_resume(struct device *dev)
|
||||
struct sdhci_host *host = dev_get_drvdata(dev);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
struct rk35xx_priv *rk_priv = priv->priv;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(pltfm_host->clk);
|
||||
@ -1372,29 +1543,24 @@ static int dwcmshc_resume(struct device *dev)
|
||||
goto disable_clk;
|
||||
}
|
||||
|
||||
if (rk_priv) {
|
||||
ret = clk_bulk_prepare_enable(RK35xx_MAX_CLKS,
|
||||
rk_priv->rockchip_clks);
|
||||
ret = clk_bulk_prepare_enable(priv->num_other_clks, priv->other_clks);
|
||||
if (ret)
|
||||
goto disable_bus_clk;
|
||||
}
|
||||
|
||||
ret = sdhci_resume_host(host);
|
||||
if (ret)
|
||||
goto disable_rockchip_clks;
|
||||
goto disable_other_clks;
|
||||
|
||||
if (host->mmc->caps2 & MMC_CAP2_CQE) {
|
||||
ret = cqhci_resume(host->mmc);
|
||||
if (ret)
|
||||
goto disable_rockchip_clks;
|
||||
goto disable_other_clks;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
disable_rockchip_clks:
|
||||
if (rk_priv)
|
||||
clk_bulk_disable_unprepare(RK35xx_MAX_CLKS,
|
||||
rk_priv->rockchip_clks);
|
||||
disable_other_clks:
|
||||
clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks);
|
||||
disable_bus_clk:
|
||||
if (!IS_ERR(priv->bus_clk))
|
||||
clk_disable_unprepare(priv->bus_clk);
|
||||
|
314
drivers/mmc/host/sdhci-of-ma35d1.c
Normal file
314
drivers/mmc/host/sdhci-of-ma35d1.c
Normal file
@ -0,0 +1,314 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2024 Nuvoton Technology Corp.
|
||||
*
|
||||
* Author: Shan-Chun Hung <shanchun1218@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/align.h>
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/build_bug.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "sdhci-pltfm.h"
|
||||
#include "sdhci.h"
|
||||
|
||||
#define MA35_SYS_MISCFCR0 0x070
|
||||
#define MA35_SDHCI_MSHCCTL 0x508
|
||||
#define MA35_SDHCI_MBIUCTL 0x510
|
||||
|
||||
#define MA35_SDHCI_CMD_CONFLICT_CHK BIT(0)
|
||||
#define MA35_SDHCI_INCR_MSK GENMASK(3, 0)
|
||||
#define MA35_SDHCI_INCR16 BIT(3)
|
||||
#define MA35_SDHCI_INCR8 BIT(2)
|
||||
|
||||
struct ma35_priv {
|
||||
struct reset_control *rst;
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *pins_uhs;
|
||||
struct pinctrl_state *pins_default;
|
||||
};
|
||||
|
||||
struct ma35_restore_data {
|
||||
u32 reg;
|
||||
u32 width;
|
||||
};
|
||||
|
||||
static const struct ma35_restore_data restore_data[] = {
|
||||
{ SDHCI_CLOCK_CONTROL, sizeof(u32)},
|
||||
{ SDHCI_BLOCK_SIZE, sizeof(u32)},
|
||||
{ SDHCI_INT_ENABLE, sizeof(u32)},
|
||||
{ SDHCI_SIGNAL_ENABLE, sizeof(u32)},
|
||||
{ SDHCI_AUTO_CMD_STATUS, sizeof(u32)},
|
||||
{ SDHCI_HOST_CONTROL, sizeof(u32)},
|
||||
{ SDHCI_TIMEOUT_CONTROL, sizeof(u8) },
|
||||
{ MA35_SDHCI_MSHCCTL, sizeof(u16)},
|
||||
{ MA35_SDHCI_MBIUCTL, sizeof(u16)},
|
||||
};
|
||||
|
||||
/*
|
||||
* If DMA addr spans 128MB boundary, we split the DMA transfer into two
|
||||
* so that each DMA transfer doesn't exceed the boundary.
|
||||
*/
|
||||
static void ma35_adma_write_desc(struct sdhci_host *host, void **desc, dma_addr_t addr, int len,
|
||||
unsigned int cmd)
|
||||
{
|
||||
int tmplen, offset;
|
||||
|
||||
if (likely(!len || (ALIGN(addr, SZ_128M) == ALIGN(addr + len - 1, SZ_128M)))) {
|
||||
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
offset = addr & (SZ_128M - 1);
|
||||
tmplen = SZ_128M - offset;
|
||||
sdhci_adma_write_desc(host, desc, addr, tmplen, cmd);
|
||||
|
||||
addr += tmplen;
|
||||
len -= tmplen;
|
||||
sdhci_adma_write_desc(host, desc, addr, len, cmd);
|
||||
}
|
||||
|
||||
static void ma35_set_clock(struct sdhci_host *host, unsigned int clock)
|
||||
{
|
||||
u32 ctl;
|
||||
|
||||
/*
|
||||
* If the clock frequency exceeds MMC_HIGH_52_MAX_DTR,
|
||||
* disable command conflict check.
|
||||
*/
|
||||
ctl = sdhci_readw(host, MA35_SDHCI_MSHCCTL);
|
||||
if (clock > MMC_HIGH_52_MAX_DTR)
|
||||
ctl &= ~MA35_SDHCI_CMD_CONFLICT_CHK;
|
||||
else
|
||||
ctl |= MA35_SDHCI_CMD_CONFLICT_CHK;
|
||||
sdhci_writew(host, ctl, MA35_SDHCI_MSHCCTL);
|
||||
|
||||
sdhci_set_clock(host, clock);
|
||||
}
|
||||
|
||||
static int ma35_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct ma35_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
switch (ios->signal_voltage) {
|
||||
case MMC_SIGNAL_VOLTAGE_180:
|
||||
if (!IS_ERR(priv->pinctrl) && !IS_ERR(priv->pins_uhs))
|
||||
pinctrl_select_state(priv->pinctrl, priv->pins_uhs);
|
||||
break;
|
||||
case MMC_SIGNAL_VOLTAGE_330:
|
||||
if (!IS_ERR(priv->pinctrl) && !IS_ERR(priv->pins_default))
|
||||
pinctrl_select_state(priv->pinctrl, priv->pins_default);
|
||||
break;
|
||||
default:
|
||||
dev_err(mmc_dev(host->mmc), "Unsupported signal voltage!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return sdhci_start_signal_voltage_switch(mmc, ios);
|
||||
}
|
||||
|
||||
static void ma35_voltage_switch(struct sdhci_host *host)
|
||||
{
|
||||
/* Wait for 5ms after set 1.8V signal enable bit */
|
||||
fsleep(5000);
|
||||
}
|
||||
|
||||
static int ma35_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
||||
{
|
||||
struct sdhci_host *host = mmc_priv(mmc);
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct ma35_priv *priv = sdhci_pltfm_priv(pltfm_host);
|
||||
int idx;
|
||||
u32 regs[ARRAY_SIZE(restore_data)] = {};
|
||||
|
||||
/*
|
||||
* Limitations require a reset of SD/eMMC before tuning and
|
||||
* saving the registers before resetting, then restoring
|
||||
* after the reset.
|
||||
*/
|
||||
for (idx = 0; idx < ARRAY_SIZE(restore_data); idx++) {
|
||||
if (restore_data[idx].width == sizeof(u32))
|
||||
regs[idx] = sdhci_readl(host, restore_data[idx].reg);
|
||||
else if (restore_data[idx].width == sizeof(u16))
|
||||
regs[idx] = sdhci_readw(host, restore_data[idx].reg);
|
||||
else if (restore_data[idx].width == sizeof(u8))
|
||||
regs[idx] = sdhci_readb(host, restore_data[idx].reg);
|
||||
}
|
||||
|
||||
reset_control_assert(priv->rst);
|
||||
reset_control_deassert(priv->rst);
|
||||
|
||||
for (idx = 0; idx < ARRAY_SIZE(restore_data); idx++) {
|
||||
if (restore_data[idx].width == sizeof(u32))
|
||||
sdhci_writel(host, regs[idx], restore_data[idx].reg);
|
||||
else if (restore_data[idx].width == sizeof(u16))
|
||||
sdhci_writew(host, regs[idx], restore_data[idx].reg);
|
||||
else if (restore_data[idx].width == sizeof(u8))
|
||||
sdhci_writeb(host, regs[idx], restore_data[idx].reg);
|
||||
}
|
||||
|
||||
return sdhci_execute_tuning(mmc, opcode);
|
||||
}
|
||||
|
||||
static const struct sdhci_ops sdhci_ma35_ops = {
|
||||
.set_clock = ma35_set_clock,
|
||||
.set_bus_width = sdhci_set_bus_width,
|
||||
.set_uhs_signaling = sdhci_set_uhs_signaling,
|
||||
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
|
||||
.reset = sdhci_reset,
|
||||
.adma_write_desc = ma35_adma_write_desc,
|
||||
.voltage_switch = ma35_voltage_switch,
|
||||
};
|
||||
|
||||
static const struct sdhci_pltfm_data sdhci_ma35_pdata = {
|
||||
.ops = &sdhci_ma35_ops,
|
||||
.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
|
||||
.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_BROKEN_DDR50 |
|
||||
SDHCI_QUIRK2_ACMD23_BROKEN,
|
||||
};
|
||||
|
||||
static int ma35_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sdhci_pltfm_host *pltfm_host;
|
||||
struct sdhci_host *host;
|
||||
struct ma35_priv *priv;
|
||||
int err;
|
||||
u32 extra, ctl;
|
||||
|
||||
host = sdhci_pltfm_init(pdev, &sdhci_ma35_pdata, sizeof(struct ma35_priv));
|
||||
if (IS_ERR(host))
|
||||
return PTR_ERR(host);
|
||||
|
||||
/* Extra adma table cnt for cross 128M boundary handling. */
|
||||
extra = DIV_ROUND_UP_ULL(dma_get_required_mask(dev), SZ_128M);
|
||||
extra = min(extra, SDHCI_MAX_SEGS);
|
||||
|
||||
host->adma_table_cnt += extra;
|
||||
pltfm_host = sdhci_priv(host);
|
||||
priv = sdhci_pltfm_priv(pltfm_host);
|
||||
|
||||
pltfm_host->clk = devm_clk_get_optional_enabled(dev, NULL);
|
||||
if (IS_ERR(pltfm_host->clk)) {
|
||||
err = dev_err_probe(dev, PTR_ERR(pltfm_host->clk), "failed to get clk\n");
|
||||
goto err_sdhci;
|
||||
}
|
||||
|
||||
err = mmc_of_parse(host->mmc);
|
||||
if (err)
|
||||
goto err_sdhci;
|
||||
|
||||
priv->rst = devm_reset_control_get_exclusive(dev, NULL);
|
||||
if (IS_ERR(priv->rst)) {
|
||||
err = dev_err_probe(dev, PTR_ERR(priv->rst), "failed to get reset control\n");
|
||||
goto err_sdhci;
|
||||
}
|
||||
|
||||
sdhci_get_of_property(pdev);
|
||||
|
||||
priv->pinctrl = devm_pinctrl_get(dev);
|
||||
if (!IS_ERR(priv->pinctrl)) {
|
||||
priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default");
|
||||
priv->pins_uhs = pinctrl_lookup_state(priv->pinctrl, "state_uhs");
|
||||
pinctrl_select_state(priv->pinctrl, priv->pins_default);
|
||||
}
|
||||
|
||||
if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V)) {
|
||||
struct regmap *regmap;
|
||||
u32 reg;
|
||||
|
||||
regmap = syscon_regmap_lookup_by_phandle(dev_of_node(dev), "nuvoton,sys");
|
||||
if (!IS_ERR(regmap)) {
|
||||
/* Enable SDHCI voltage stable for 1.8V */
|
||||
regmap_read(regmap, MA35_SYS_MISCFCR0, ®);
|
||||
reg |= BIT(17);
|
||||
regmap_write(regmap, MA35_SYS_MISCFCR0, reg);
|
||||
}
|
||||
|
||||
host->mmc_host_ops.start_signal_voltage_switch =
|
||||
ma35_start_signal_voltage_switch;
|
||||
}
|
||||
|
||||
host->mmc_host_ops.execute_tuning = ma35_execute_tuning;
|
||||
|
||||
err = sdhci_add_host(host);
|
||||
if (err)
|
||||
goto err_sdhci;
|
||||
|
||||
/*
|
||||
* Split data into chunks of 16 or 8 bytes for transmission.
|
||||
* Each chunk transfer is guaranteed to be uninterrupted on the bus.
|
||||
* This likely corresponds to the AHB bus DMA burst size.
|
||||
*/
|
||||
ctl = sdhci_readw(host, MA35_SDHCI_MBIUCTL);
|
||||
ctl &= ~MA35_SDHCI_INCR_MSK;
|
||||
ctl |= MA35_SDHCI_INCR16 | MA35_SDHCI_INCR8;
|
||||
sdhci_writew(host, ctl, MA35_SDHCI_MBIUCTL);
|
||||
|
||||
return 0;
|
||||
|
||||
err_sdhci:
|
||||
sdhci_pltfm_free(pdev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ma35_disable_card_clk(struct sdhci_host *host)
|
||||
{
|
||||
u16 ctrl;
|
||||
|
||||
ctrl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
||||
if (ctrl & SDHCI_CLOCK_CARD_EN) {
|
||||
ctrl &= ~SDHCI_CLOCK_CARD_EN;
|
||||
sdhci_writew(host, ctrl, SDHCI_CLOCK_CONTROL);
|
||||
}
|
||||
}
|
||||
|
||||
static void ma35_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sdhci_host *host = platform_get_drvdata(pdev);
|
||||
|
||||
sdhci_remove_host(host, 0);
|
||||
ma35_disable_card_clk(host);
|
||||
sdhci_pltfm_free(pdev);
|
||||
}
|
||||
|
||||
static const struct of_device_id sdhci_ma35_dt_ids[] = {
|
||||
{ .compatible = "nuvoton,ma35d1-sdhci" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver sdhci_ma35_driver = {
|
||||
.driver = {
|
||||
.name = "sdhci-ma35",
|
||||
.of_match_table = sdhci_ma35_dt_ids,
|
||||
},
|
||||
.probe = ma35_probe,
|
||||
.remove_new = ma35_remove,
|
||||
};
|
||||
module_platform_driver(sdhci_ma35_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SDHCI platform driver for Nuvoton MA35");
|
||||
MODULE_AUTHOR("Shan-Chun Hung <shanchun1218@gmail.com>");
|
||||
MODULE_LICENSE("GPL");
|
@ -126,7 +126,7 @@ static void pxav1_request_done(struct sdhci_host *host, struct mmc_request *mrq)
|
||||
struct sdhci_pxav2_host *pxav2_host;
|
||||
|
||||
/* If this is an SDIO command, perform errata workaround for silicon bug */
|
||||
if (mrq->cmd && !mrq->cmd->error &&
|
||||
if (!mrq->cmd->error &&
|
||||
(mrq->cmd->opcode == SD_IO_RW_DIRECT ||
|
||||
mrq->cmd->opcode == SD_IO_RW_EXTENDED)) {
|
||||
/* Reset data port */
|
||||
|
@ -86,6 +86,7 @@
|
||||
|
||||
#define CLOCK_TOO_SLOW_HZ 50000000
|
||||
#define SDHCI_AM654_AUTOSUSPEND_DELAY -1
|
||||
#define RETRY_TUNING_MAX 10
|
||||
|
||||
/* Command Queue Host Controller Interface Base address */
|
||||
#define SDHCI_AM654_CQE_BASE_ADDR 0x200
|
||||
@ -151,6 +152,7 @@ struct sdhci_am654_data {
|
||||
u32 flags;
|
||||
u32 quirks;
|
||||
bool dll_enable;
|
||||
u32 tuning_loop;
|
||||
|
||||
#define SDHCI_AM654_QUIRK_FORCE_CDTEST BIT(0)
|
||||
};
|
||||
@ -443,7 +445,7 @@ static u32 sdhci_am654_cqhci_irq(struct sdhci_host *host, u32 intmask)
|
||||
#define ITAPDLY_LENGTH 32
|
||||
#define ITAPDLY_LAST_INDEX (ITAPDLY_LENGTH - 1)
|
||||
|
||||
static u32 sdhci_am654_calculate_itap(struct sdhci_host *host, struct window
|
||||
static int sdhci_am654_calculate_itap(struct sdhci_host *host, struct window
|
||||
*fail_window, u8 num_fails, bool circular_buffer)
|
||||
{
|
||||
u8 itap = 0, start_fail = 0, end_fail = 0, pass_length = 0;
|
||||
@ -453,12 +455,16 @@ static u32 sdhci_am654_calculate_itap(struct sdhci_host *host, struct window
|
||||
int prev_fail_end = -1;
|
||||
u8 i;
|
||||
|
||||
if (!num_fails)
|
||||
return ITAPDLY_LAST_INDEX >> 1;
|
||||
if (!num_fails) {
|
||||
/* Retry tuning */
|
||||
dev_dbg(dev, "No failing region found, retry tuning\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fail_window->length == ITAPDLY_LENGTH) {
|
||||
dev_err(dev, "No passing ITAPDLY, return 0\n");
|
||||
return 0;
|
||||
/* Retry tuning */
|
||||
dev_dbg(dev, "No passing itapdly, retry tuning\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
first_fail_start = fail_window->start;
|
||||
@ -494,13 +500,14 @@ static u32 sdhci_am654_calculate_itap(struct sdhci_host *host, struct window
|
||||
return (itap > ITAPDLY_LAST_INDEX) ? ITAPDLY_LAST_INDEX >> 1 : itap;
|
||||
}
|
||||
|
||||
static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host,
|
||||
static int sdhci_am654_do_tuning(struct sdhci_host *host,
|
||||
u32 opcode)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
|
||||
unsigned char timing = host->mmc->ios.timing;
|
||||
struct window fail_window[ITAPDLY_LENGTH];
|
||||
struct device *dev = mmc_dev(host->mmc);
|
||||
u8 curr_pass, itap;
|
||||
u8 fail_index = 0;
|
||||
u8 prev_pass = 1;
|
||||
@ -521,6 +528,7 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host,
|
||||
if (!curr_pass) {
|
||||
fail_window[fail_index].end = itap;
|
||||
fail_window[fail_index].length++;
|
||||
dev_dbg(dev, "Failed itapdly=%d\n", itap);
|
||||
}
|
||||
|
||||
if (curr_pass && !prev_pass)
|
||||
@ -532,13 +540,34 @@ static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host,
|
||||
if (fail_window[fail_index].length != 0)
|
||||
fail_index++;
|
||||
|
||||
itap = sdhci_am654_calculate_itap(host, fail_window, fail_index,
|
||||
return sdhci_am654_calculate_itap(host, fail_window, fail_index,
|
||||
sdhci_am654->dll_enable);
|
||||
}
|
||||
|
||||
sdhci_am654_write_itapdly(sdhci_am654, itap, sdhci_am654->itap_del_ena[timing]);
|
||||
static int sdhci_am654_platform_execute_tuning(struct sdhci_host *host,
|
||||
u32 opcode)
|
||||
{
|
||||
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
||||
struct sdhci_am654_data *sdhci_am654 = sdhci_pltfm_priv(pltfm_host);
|
||||
unsigned char timing = host->mmc->ios.timing;
|
||||
struct device *dev = mmc_dev(host->mmc);
|
||||
int itapdly;
|
||||
|
||||
do {
|
||||
itapdly = sdhci_am654_do_tuning(host, opcode);
|
||||
if (itapdly >= 0)
|
||||
break;
|
||||
} while (++sdhci_am654->tuning_loop < RETRY_TUNING_MAX);
|
||||
|
||||
if (itapdly < 0) {
|
||||
dev_err(dev, "Failed to find itapdly, fail tuning\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "Passed tuning, final itapdly=%d\n", itapdly);
|
||||
sdhci_am654_write_itapdly(sdhci_am654, itapdly, sdhci_am654->itap_del_ena[timing]);
|
||||
/* Save ITAPDLY */
|
||||
sdhci_am654->itap_del_sel[timing] = itap;
|
||||
sdhci_am654->itap_del_sel[timing] = itapdly;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -742,6 +771,9 @@ static int sdhci_am654_init(struct sdhci_host *host)
|
||||
regmap_update_bits(sdhci_am654->base, CTL_CFG_3, TUNINGFORSDR50_MASK,
|
||||
TUNINGFORSDR50_MASK);
|
||||
|
||||
/* Use to re-execute tuning */
|
||||
sdhci_am654->tuning_loop = 0;
|
||||
|
||||
ret = sdhci_setup_host(host);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -895,8 +895,8 @@ static void tmio_mmc_power_on(struct tmio_mmc_host *host, unsigned short vdd)
|
||||
* It seems, VccQ should be switched on after Vcc, this is also what the
|
||||
* omap_hsmmc.c driver does.
|
||||
*/
|
||||
if (!IS_ERR(mmc->supply.vqmmc) && !ret) {
|
||||
ret = regulator_enable(mmc->supply.vqmmc);
|
||||
if (!ret) {
|
||||
ret = mmc_regulator_enable_vqmmc(mmc);
|
||||
usleep_range(200, 300);
|
||||
}
|
||||
|
||||
@ -909,8 +909,7 @@ static void tmio_mmc_power_off(struct tmio_mmc_host *host)
|
||||
{
|
||||
struct mmc_host *mmc = host->mmc;
|
||||
|
||||
if (!IS_ERR(mmc->supply.vqmmc))
|
||||
regulator_disable(mmc->supply.vqmmc);
|
||||
mmc_regulator_disable_vqmmc(mmc);
|
||||
|
||||
if (!IS_ERR(mmc->supply.vmmc))
|
||||
mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
|
||||
|
@ -4,6 +4,7 @@ config OPTEE
|
||||
tristate "OP-TEE"
|
||||
depends on HAVE_ARM_SMCCC
|
||||
depends on MMU
|
||||
depends on RPMB || !RPMB
|
||||
help
|
||||
This implements the OP-TEE Trusted Execution Environment (TEE)
|
||||
driver.
|
||||
|
@ -10,17 +10,85 @@
|
||||
#include <linux/errno.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/tee_core.h>
|
||||
#include <linux/types.h>
|
||||
#include "optee_private.h"
|
||||
|
||||
struct blocking_notifier_head optee_rpmb_intf_added =
|
||||
BLOCKING_NOTIFIER_INIT(optee_rpmb_intf_added);
|
||||
|
||||
static int rpmb_add_dev(struct device *dev)
|
||||
{
|
||||
blocking_notifier_call_chain(&optee_rpmb_intf_added, 0,
|
||||
to_rpmb_dev(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct class_interface rpmb_class_intf = {
|
||||
.add_dev = rpmb_add_dev,
|
||||
};
|
||||
|
||||
void optee_bus_scan_rpmb(struct work_struct *work)
|
||||
{
|
||||
struct optee *optee = container_of(work, struct optee,
|
||||
rpmb_scan_bus_work);
|
||||
int ret;
|
||||
|
||||
if (!optee->rpmb_scan_bus_done) {
|
||||
ret = optee_enumerate_devices(PTA_CMD_GET_DEVICES_RPMB);
|
||||
optee->rpmb_scan_bus_done = !ret;
|
||||
if (ret && ret != -ENODEV)
|
||||
pr_info("Scanning for RPMB device: ret %d\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
int optee_rpmb_intf_rdev(struct notifier_block *intf, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct optee *optee = container_of(intf, struct optee, rpmb_intf);
|
||||
|
||||
schedule_work(&optee->rpmb_scan_bus_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void optee_bus_scan(struct work_struct *work)
|
||||
{
|
||||
WARN_ON(optee_enumerate_devices(PTA_CMD_GET_DEVICES_SUPP));
|
||||
}
|
||||
|
||||
static ssize_t rpmb_routing_model_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct optee *optee = dev_get_drvdata(dev);
|
||||
const char *s;
|
||||
|
||||
if (optee->in_kernel_rpmb_routing)
|
||||
s = "kernel";
|
||||
else
|
||||
s = "user";
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%s\n", s);
|
||||
}
|
||||
static DEVICE_ATTR_RO(rpmb_routing_model);
|
||||
|
||||
static struct attribute *optee_dev_attrs[] = {
|
||||
&dev_attr_rpmb_routing_model.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(optee_dev);
|
||||
|
||||
void optee_set_dev_group(struct optee *optee)
|
||||
{
|
||||
tee_device_set_dev_groups(optee->teedev, optee_dev_groups);
|
||||
tee_device_set_dev_groups(optee->supp_teedev, optee_dev_groups);
|
||||
}
|
||||
|
||||
int optee_open(struct tee_context *ctx, bool cap_memref_null)
|
||||
{
|
||||
struct optee_context_data *ctxdata;
|
||||
@ -97,6 +165,9 @@ void optee_release_supp(struct tee_context *ctx)
|
||||
|
||||
void optee_remove_common(struct optee *optee)
|
||||
{
|
||||
blocking_notifier_chain_unregister(&optee_rpmb_intf_added,
|
||||
&optee->rpmb_intf);
|
||||
cancel_work_sync(&optee->rpmb_scan_bus_work);
|
||||
/* Unregister OP-TEE specific client devices on TEE bus */
|
||||
optee_unregister_devices();
|
||||
|
||||
@ -113,13 +184,18 @@ void optee_remove_common(struct optee *optee)
|
||||
tee_shm_pool_free(optee->pool);
|
||||
optee_supp_uninit(&optee->supp);
|
||||
mutex_destroy(&optee->call_queue.mutex);
|
||||
rpmb_dev_put(optee->rpmb_dev);
|
||||
mutex_destroy(&optee->rpmb_dev_mutex);
|
||||
}
|
||||
|
||||
static int smc_abi_rc;
|
||||
static int ffa_abi_rc;
|
||||
static bool intf_is_regged;
|
||||
|
||||
static int __init optee_core_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/*
|
||||
* The kernel may have crashed at the same time that all available
|
||||
* secure world threads were suspended and we cannot reschedule the
|
||||
@ -130,18 +206,36 @@ static int __init optee_core_init(void)
|
||||
if (is_kdump_kernel())
|
||||
return -ENODEV;
|
||||
|
||||
if (IS_REACHABLE(CONFIG_RPMB)) {
|
||||
rc = rpmb_interface_register(&rpmb_class_intf);
|
||||
if (rc)
|
||||
return rc;
|
||||
intf_is_regged = true;
|
||||
}
|
||||
|
||||
smc_abi_rc = optee_smc_abi_register();
|
||||
ffa_abi_rc = optee_ffa_abi_register();
|
||||
|
||||
/* If both failed there's no point with this module */
|
||||
if (smc_abi_rc && ffa_abi_rc)
|
||||
if (smc_abi_rc && ffa_abi_rc) {
|
||||
if (IS_REACHABLE(CONFIG_RPMB)) {
|
||||
rpmb_interface_unregister(&rpmb_class_intf);
|
||||
intf_is_regged = false;
|
||||
}
|
||||
return smc_abi_rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(optee_core_init);
|
||||
|
||||
static void __exit optee_core_exit(void)
|
||||
{
|
||||
if (IS_REACHABLE(CONFIG_RPMB) && intf_is_regged) {
|
||||
rpmb_interface_unregister(&rpmb_class_intf);
|
||||
intf_is_regged = false;
|
||||
}
|
||||
|
||||
if (!smc_abi_rc)
|
||||
optee_smc_abi_unregister();
|
||||
if (!ffa_abi_rc)
|
||||
|
@ -43,6 +43,13 @@ static int get_devices(struct tee_context *ctx, u32 session,
|
||||
ret = tee_client_invoke_func(ctx, &inv_arg, param);
|
||||
if ((ret < 0) || ((inv_arg.ret != TEEC_SUCCESS) &&
|
||||
(inv_arg.ret != TEEC_ERROR_SHORT_BUFFER))) {
|
||||
/*
|
||||
* TEE_ERROR_STORAGE_NOT_AVAILABLE is returned when getting
|
||||
* the list of device TAs that depends on RPMB but a usable
|
||||
* RPMB device isn't found.
|
||||
*/
|
||||
if (inv_arg.ret == TEE_ERROR_STORAGE_NOT_AVAILABLE)
|
||||
return -ENODEV;
|
||||
pr_err("PTA_CMD_GET_DEVICES invoke function err: %x\n",
|
||||
inv_arg.ret);
|
||||
return -EINVAL;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <linux/arm_ffa.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
@ -909,6 +910,10 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev)
|
||||
optee->ffa.bottom_half_value = U32_MAX;
|
||||
optee->rpc_param_count = rpc_param_count;
|
||||
|
||||
if (IS_REACHABLE(CONFIG_RPMB) &&
|
||||
(sec_caps & OPTEE_FFA_SEC_CAP_RPMB_PROBE))
|
||||
optee->in_kernel_rpmb_routing = true;
|
||||
|
||||
teedev = tee_device_alloc(&optee_ffa_clnt_desc, NULL, optee->pool,
|
||||
optee);
|
||||
if (IS_ERR(teedev)) {
|
||||
@ -925,6 +930,8 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev)
|
||||
}
|
||||
optee->supp_teedev = teedev;
|
||||
|
||||
optee_set_dev_group(optee);
|
||||
|
||||
rc = tee_device_register(optee->teedev);
|
||||
if (rc)
|
||||
goto err_unreg_supp_teedev;
|
||||
@ -940,6 +947,7 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev)
|
||||
optee_cq_init(&optee->call_queue, 0);
|
||||
optee_supp_init(&optee->supp);
|
||||
optee_shm_arg_cache_init(optee, arg_cache_flags);
|
||||
mutex_init(&optee->rpmb_dev_mutex);
|
||||
ffa_dev_set_drvdata(ffa_dev, optee);
|
||||
ctx = teedev_open(optee->teedev);
|
||||
if (IS_ERR(ctx)) {
|
||||
@ -961,6 +969,10 @@ static int optee_ffa_probe(struct ffa_device *ffa_dev)
|
||||
if (rc)
|
||||
goto err_unregister_devices;
|
||||
|
||||
INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb);
|
||||
optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev;
|
||||
blocking_notifier_chain_register(&optee_rpmb_intf_added,
|
||||
&optee->rpmb_intf);
|
||||
pr_info("initialized driver\n");
|
||||
return 0;
|
||||
|
||||
@ -974,6 +986,8 @@ err_close_ctx:
|
||||
teedev_close_context(ctx);
|
||||
err_rhashtable_free:
|
||||
rhashtable_free_and_destroy(&optee->ffa.global_ids, rh_free_fn, NULL);
|
||||
rpmb_dev_put(optee->rpmb_dev);
|
||||
mutex_destroy(&optee->rpmb_dev_mutex);
|
||||
optee_supp_uninit(&optee->supp);
|
||||
mutex_destroy(&optee->call_queue.mutex);
|
||||
mutex_destroy(&optee->ffa.mutex);
|
||||
|
@ -92,6 +92,8 @@
|
||||
#define OPTEE_FFA_SEC_CAP_ARG_OFFSET BIT(0)
|
||||
/* OP-TEE supports asynchronous notification via FF-A */
|
||||
#define OPTEE_FFA_SEC_CAP_ASYNC_NOTIF BIT(1)
|
||||
/* OP-TEE supports probing for RPMB device if needed */
|
||||
#define OPTEE_FFA_SEC_CAP_RPMB_PROBE BIT(2)
|
||||
|
||||
#define OPTEE_FFA_EXCHANGE_CAPABILITIES OPTEE_FFA_BLOCKING_CALL(2)
|
||||
|
||||
|
@ -7,7 +7,9 @@
|
||||
#define OPTEE_PRIVATE_H
|
||||
|
||||
#include <linux/arm-smccc.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/rhashtable.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/tee_core.h>
|
||||
#include <linux/types.h>
|
||||
@ -20,6 +22,7 @@
|
||||
/* Some Global Platform error codes used in this driver */
|
||||
#define TEEC_SUCCESS 0x00000000
|
||||
#define TEEC_ERROR_BAD_PARAMETERS 0xFFFF0006
|
||||
#define TEEC_ERROR_ITEM_NOT_FOUND 0xFFFF0008
|
||||
#define TEEC_ERROR_NOT_SUPPORTED 0xFFFF000A
|
||||
#define TEEC_ERROR_COMMUNICATION 0xFFFF000E
|
||||
#define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C
|
||||
@ -28,6 +31,7 @@
|
||||
|
||||
/* API Return Codes are from the GP TEE Internal Core API Specification */
|
||||
#define TEE_ERROR_TIMEOUT 0xFFFF3001
|
||||
#define TEE_ERROR_STORAGE_NOT_AVAILABLE 0xF0100003
|
||||
|
||||
#define TEEC_ORIGIN_COMMS 0x00000002
|
||||
|
||||
@ -200,6 +204,12 @@ struct optee_ops {
|
||||
* @notif: notification synchronization struct
|
||||
* @supp: supplicant synchronization struct for RPC to supplicant
|
||||
* @pool: shared memory pool
|
||||
* @mutex: mutex protecting @rpmb_dev
|
||||
* @rpmb_dev: current RPMB device or NULL
|
||||
* @rpmb_scan_bus_done flag if device registation of RPMB dependent devices
|
||||
* was already done
|
||||
* @rpmb_scan_bus_work workq to for an RPMB device and to scan optee bus
|
||||
* and register RPMB dependent optee drivers
|
||||
* @rpc_param_count: If > 0 number of RPC parameters to make room for
|
||||
* @scan_bus_done flag if device registation was already done.
|
||||
* @scan_bus_work workq to scan optee bus and register optee drivers
|
||||
@ -218,9 +228,16 @@ struct optee {
|
||||
struct optee_notif notif;
|
||||
struct optee_supp supp;
|
||||
struct tee_shm_pool *pool;
|
||||
/* Protects rpmb_dev pointer */
|
||||
struct mutex rpmb_dev_mutex;
|
||||
struct rpmb_dev *rpmb_dev;
|
||||
struct notifier_block rpmb_intf;
|
||||
unsigned int rpc_param_count;
|
||||
bool scan_bus_done;
|
||||
bool rpmb_scan_bus_done;
|
||||
bool in_kernel_rpmb_routing;
|
||||
struct work_struct scan_bus_work;
|
||||
struct work_struct rpmb_scan_bus_work;
|
||||
};
|
||||
|
||||
struct optee_session {
|
||||
@ -253,6 +270,8 @@ struct optee_call_ctx {
|
||||
size_t num_entries;
|
||||
};
|
||||
|
||||
extern struct blocking_notifier_head optee_rpmb_intf_added;
|
||||
|
||||
int optee_notif_init(struct optee *optee, u_int max_key);
|
||||
void optee_notif_uninit(struct optee *optee);
|
||||
int optee_notif_wait(struct optee *optee, u_int key, u32 timeout);
|
||||
@ -283,9 +302,14 @@ int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session);
|
||||
|
||||
#define PTA_CMD_GET_DEVICES 0x0
|
||||
#define PTA_CMD_GET_DEVICES_SUPP 0x1
|
||||
#define PTA_CMD_GET_DEVICES_RPMB 0x2
|
||||
int optee_enumerate_devices(u32 func);
|
||||
void optee_unregister_devices(void);
|
||||
void optee_bus_scan_rpmb(struct work_struct *work);
|
||||
int optee_rpmb_intf_rdev(struct notifier_block *intf, unsigned long action,
|
||||
void *data);
|
||||
|
||||
void optee_set_dev_group(struct optee *optee);
|
||||
void optee_remove_common(struct optee *optee);
|
||||
int optee_open(struct tee_context *ctx, bool cap_memref_null);
|
||||
void optee_release(struct tee_context *ctx);
|
||||
|
@ -104,4 +104,39 @@
|
||||
/* I2C master control flags */
|
||||
#define OPTEE_RPC_I2C_FLAGS_TEN_BIT BIT(0)
|
||||
|
||||
/*
|
||||
* Reset RPMB probing
|
||||
*
|
||||
* Releases an eventually already used RPMB devices and starts over searching
|
||||
* for RPMB devices. Returns the kind of shared memory to use in subsequent
|
||||
* OPTEE_RPC_CMD_RPMB_PROBE_NEXT and OPTEE_RPC_CMD_RPMB calls.
|
||||
*
|
||||
* [out] value[0].a OPTEE_RPC_SHM_TYPE_*, the parameter for
|
||||
* OPTEE_RPC_CMD_SHM_ALLOC
|
||||
*/
|
||||
#define OPTEE_RPC_CMD_RPMB_PROBE_RESET 22
|
||||
|
||||
/*
|
||||
* Probe next RPMB device
|
||||
*
|
||||
* [out] value[0].a Type of RPMB device, OPTEE_RPC_RPMB_*
|
||||
* [out] value[0].b EXT CSD-slice 168 "RPMB Size"
|
||||
* [out] value[0].c EXT CSD-slice 222 "Reliable Write Sector Count"
|
||||
* [out] memref[1] Buffer with the raw CID
|
||||
*/
|
||||
#define OPTEE_RPC_CMD_RPMB_PROBE_NEXT 23
|
||||
|
||||
/* Type of RPMB device */
|
||||
#define OPTEE_RPC_RPMB_EMMC 0
|
||||
#define OPTEE_RPC_RPMB_UFS 1
|
||||
#define OPTEE_RPC_RPMB_NVME 2
|
||||
|
||||
/*
|
||||
* Replay Protected Memory Block access
|
||||
*
|
||||
* [in] memref[0] Frames to device
|
||||
* [out] memref[1] Frames from device
|
||||
*/
|
||||
#define OPTEE_RPC_CMD_RPMB_FRAMES 24
|
||||
|
||||
#endif /*__OPTEE_RPC_CMD_H*/
|
||||
|
@ -278,6 +278,8 @@ struct optee_smc_get_shm_config_result {
|
||||
#define OPTEE_SMC_SEC_CAP_ASYNC_NOTIF BIT(5)
|
||||
/* Secure world supports pre-allocating RPC arg struct */
|
||||
#define OPTEE_SMC_SEC_CAP_RPC_ARG BIT(6)
|
||||
/* Secure world supports probing for RPMB device if needed */
|
||||
#define OPTEE_SMC_SEC_CAP_RPMB_PROBE BIT(7)
|
||||
|
||||
#define OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES 9
|
||||
#define OPTEE_SMC_EXCHANGE_CAPABILITIES \
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/tee_core.h>
|
||||
#include "optee_private.h"
|
||||
@ -261,6 +262,154 @@ void optee_rpc_cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm)
|
||||
optee_supp_thrd_req(ctx, OPTEE_RPC_CMD_SHM_FREE, 1, ¶m);
|
||||
}
|
||||
|
||||
static void handle_rpc_func_rpmb_probe_reset(struct tee_context *ctx,
|
||||
struct optee *optee,
|
||||
struct optee_msg_arg *arg)
|
||||
{
|
||||
struct tee_param params[1];
|
||||
|
||||
if (arg->num_params != ARRAY_SIZE(params) ||
|
||||
optee->ops->from_msg_param(optee, params, arg->num_params,
|
||||
arg->params) ||
|
||||
params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
return;
|
||||
}
|
||||
|
||||
params[0].u.value.a = OPTEE_RPC_SHM_TYPE_KERNEL;
|
||||
params[0].u.value.b = 0;
|
||||
params[0].u.value.c = 0;
|
||||
if (optee->ops->to_msg_param(optee, arg->params,
|
||||
arg->num_params, params)) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&optee->rpmb_dev_mutex);
|
||||
rpmb_dev_put(optee->rpmb_dev);
|
||||
optee->rpmb_dev = NULL;
|
||||
mutex_unlock(&optee->rpmb_dev_mutex);
|
||||
|
||||
arg->ret = TEEC_SUCCESS;
|
||||
}
|
||||
|
||||
static int rpmb_type_to_rpc_type(enum rpmb_type rtype)
|
||||
{
|
||||
switch (rtype) {
|
||||
case RPMB_TYPE_EMMC:
|
||||
return OPTEE_RPC_RPMB_EMMC;
|
||||
case RPMB_TYPE_UFS:
|
||||
return OPTEE_RPC_RPMB_UFS;
|
||||
case RPMB_TYPE_NVME:
|
||||
return OPTEE_RPC_RPMB_NVME;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int rpc_rpmb_match(struct device *dev, const void *data)
|
||||
{
|
||||
struct rpmb_dev *rdev = to_rpmb_dev(dev);
|
||||
|
||||
return rpmb_type_to_rpc_type(rdev->descr.type) >= 0;
|
||||
}
|
||||
|
||||
static void handle_rpc_func_rpmb_probe_next(struct tee_context *ctx,
|
||||
struct optee *optee,
|
||||
struct optee_msg_arg *arg)
|
||||
{
|
||||
struct rpmb_dev *rdev;
|
||||
struct tee_param params[2];
|
||||
void *buf;
|
||||
|
||||
if (arg->num_params != ARRAY_SIZE(params) ||
|
||||
optee->ops->from_msg_param(optee, params, arg->num_params,
|
||||
arg->params) ||
|
||||
params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT ||
|
||||
params[1].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
return;
|
||||
}
|
||||
buf = tee_shm_get_va(params[1].u.memref.shm,
|
||||
params[1].u.memref.shm_offs);
|
||||
if (IS_ERR(buf)) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&optee->rpmb_dev_mutex);
|
||||
rdev = rpmb_dev_find_device(NULL, optee->rpmb_dev, rpc_rpmb_match);
|
||||
rpmb_dev_put(optee->rpmb_dev);
|
||||
optee->rpmb_dev = rdev;
|
||||
mutex_unlock(&optee->rpmb_dev_mutex);
|
||||
|
||||
if (!rdev) {
|
||||
arg->ret = TEEC_ERROR_ITEM_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
if (params[1].u.memref.size < rdev->descr.dev_id_len) {
|
||||
arg->ret = TEEC_ERROR_SHORT_BUFFER;
|
||||
return;
|
||||
}
|
||||
memcpy(buf, rdev->descr.dev_id, rdev->descr.dev_id_len);
|
||||
params[1].u.memref.size = rdev->descr.dev_id_len;
|
||||
params[0].u.value.a = rpmb_type_to_rpc_type(rdev->descr.type);
|
||||
params[0].u.value.b = rdev->descr.capacity;
|
||||
params[0].u.value.c = rdev->descr.reliable_wr_count;
|
||||
if (optee->ops->to_msg_param(optee, arg->params,
|
||||
arg->num_params, params)) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
return;
|
||||
}
|
||||
|
||||
arg->ret = TEEC_SUCCESS;
|
||||
}
|
||||
|
||||
static void handle_rpc_func_rpmb_frames(struct tee_context *ctx,
|
||||
struct optee *optee,
|
||||
struct optee_msg_arg *arg)
|
||||
{
|
||||
struct tee_param params[2];
|
||||
struct rpmb_dev *rdev;
|
||||
void *p0, *p1;
|
||||
|
||||
mutex_lock(&optee->rpmb_dev_mutex);
|
||||
rdev = rpmb_dev_get(optee->rpmb_dev);
|
||||
mutex_unlock(&optee->rpmb_dev_mutex);
|
||||
if (!rdev) {
|
||||
arg->ret = TEEC_ERROR_ITEM_NOT_FOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg->num_params != ARRAY_SIZE(params) ||
|
||||
optee->ops->from_msg_param(optee, params, arg->num_params,
|
||||
arg->params) ||
|
||||
params[0].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT ||
|
||||
params[1].attr != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p0 = tee_shm_get_va(params[0].u.memref.shm,
|
||||
params[0].u.memref.shm_offs);
|
||||
p1 = tee_shm_get_va(params[1].u.memref.shm,
|
||||
params[1].u.memref.shm_offs);
|
||||
if (rpmb_route_frames(rdev, p0, params[0].u.memref.size, p1,
|
||||
params[1].u.memref.size)) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
goto out;
|
||||
}
|
||||
if (optee->ops->to_msg_param(optee, arg->params,
|
||||
arg->num_params, params)) {
|
||||
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
|
||||
goto out;
|
||||
}
|
||||
arg->ret = TEEC_SUCCESS;
|
||||
out:
|
||||
rpmb_dev_put(rdev);
|
||||
}
|
||||
|
||||
void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee,
|
||||
struct optee_msg_arg *arg)
|
||||
{
|
||||
@ -277,6 +426,34 @@ void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee,
|
||||
case OPTEE_RPC_CMD_I2C_TRANSFER:
|
||||
handle_rpc_func_cmd_i2c_transfer(ctx, arg);
|
||||
break;
|
||||
/*
|
||||
* optee->in_kernel_rpmb_routing true means that OP-TEE supports
|
||||
* in-kernel RPMB routing _and_ that the RPMB subsystem is
|
||||
* reachable. This is reported to user space with
|
||||
* rpmb_routing_model=kernel in sysfs.
|
||||
*
|
||||
* rpmb_routing_model=kernel is also a promise to user space that
|
||||
* RPMB access will not require supplicant support, hence the
|
||||
* checks below.
|
||||
*/
|
||||
case OPTEE_RPC_CMD_RPMB_PROBE_RESET:
|
||||
if (optee->in_kernel_rpmb_routing)
|
||||
handle_rpc_func_rpmb_probe_reset(ctx, optee, arg);
|
||||
else
|
||||
handle_rpc_supp_cmd(ctx, optee, arg);
|
||||
break;
|
||||
case OPTEE_RPC_CMD_RPMB_PROBE_NEXT:
|
||||
if (optee->in_kernel_rpmb_routing)
|
||||
handle_rpc_func_rpmb_probe_next(ctx, optee, arg);
|
||||
else
|
||||
handle_rpc_supp_cmd(ctx, optee, arg);
|
||||
break;
|
||||
case OPTEE_RPC_CMD_RPMB_FRAMES:
|
||||
if (optee->in_kernel_rpmb_routing)
|
||||
handle_rpc_func_rpmb_frames(ctx, optee, arg);
|
||||
else
|
||||
handle_rpc_supp_cmd(ctx, optee, arg);
|
||||
break;
|
||||
default:
|
||||
handle_rpc_supp_cmd(ctx, optee, arg);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rpmb.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
@ -1685,6 +1686,10 @@ static int optee_probe(struct platform_device *pdev)
|
||||
optee->smc.sec_caps = sec_caps;
|
||||
optee->rpc_param_count = rpc_param_count;
|
||||
|
||||
if (IS_REACHABLE(CONFIG_RPMB) &&
|
||||
(sec_caps & OPTEE_SMC_SEC_CAP_RPMB_PROBE))
|
||||
optee->in_kernel_rpmb_routing = true;
|
||||
|
||||
teedev = tee_device_alloc(&optee_clnt_desc, NULL, pool, optee);
|
||||
if (IS_ERR(teedev)) {
|
||||
rc = PTR_ERR(teedev);
|
||||
@ -1699,6 +1704,8 @@ static int optee_probe(struct platform_device *pdev)
|
||||
}
|
||||
optee->supp_teedev = teedev;
|
||||
|
||||
optee_set_dev_group(optee);
|
||||
|
||||
rc = tee_device_register(optee->teedev);
|
||||
if (rc)
|
||||
goto err_unreg_supp_teedev;
|
||||
@ -1712,6 +1719,7 @@ static int optee_probe(struct platform_device *pdev)
|
||||
optee->smc.memremaped_shm = memremaped_shm;
|
||||
optee->pool = pool;
|
||||
optee_shm_arg_cache_init(optee, arg_cache_flags);
|
||||
mutex_init(&optee->rpmb_dev_mutex);
|
||||
|
||||
platform_set_drvdata(pdev, optee);
|
||||
ctx = teedev_open(optee->teedev);
|
||||
@ -1766,6 +1774,10 @@ static int optee_probe(struct platform_device *pdev)
|
||||
if (rc)
|
||||
goto err_disable_shm_cache;
|
||||
|
||||
INIT_WORK(&optee->rpmb_scan_bus_work, optee_bus_scan_rpmb);
|
||||
optee->rpmb_intf.notifier_call = optee_rpmb_intf_rdev;
|
||||
blocking_notifier_chain_register(&optee_rpmb_intf_added,
|
||||
&optee->rpmb_intf);
|
||||
pr_info("initialized driver\n");
|
||||
return 0;
|
||||
|
||||
@ -1779,6 +1791,8 @@ err_notif_uninit:
|
||||
err_close_ctx:
|
||||
teedev_close_context(ctx);
|
||||
err_supp_uninit:
|
||||
rpmb_dev_put(optee->rpmb_dev);
|
||||
mutex_destroy(&optee->rpmb_dev_mutex);
|
||||
optee_shm_arg_cache_uninit(optee);
|
||||
optee_supp_uninit(&optee->supp);
|
||||
mutex_destroy(&optee->call_queue.mutex);
|
||||
|
@ -40,10 +40,7 @@ static const uuid_t tee_client_uuid_ns = UUID_INIT(0x58ac9ca0, 0x2086, 0x4683,
|
||||
static DECLARE_BITMAP(dev_mask, TEE_NUM_DEVICES);
|
||||
static DEFINE_SPINLOCK(driver_lock);
|
||||
|
||||
static const struct class tee_class = {
|
||||
.name = "tee",
|
||||
};
|
||||
|
||||
static const struct class tee_class;
|
||||
static dev_t tee_devt;
|
||||
|
||||
struct tee_context *teedev_open(struct tee_device *teedev)
|
||||
@ -965,6 +962,13 @@ err:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tee_device_alloc);
|
||||
|
||||
void tee_device_set_dev_groups(struct tee_device *teedev,
|
||||
const struct attribute_group **dev_groups)
|
||||
{
|
||||
teedev->dev.groups = dev_groups;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tee_device_set_dev_groups);
|
||||
|
||||
static ssize_t implementation_id_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
@ -983,6 +987,11 @@ static struct attribute *tee_dev_attrs[] = {
|
||||
|
||||
ATTRIBUTE_GROUPS(tee_dev);
|
||||
|
||||
static const struct class tee_class = {
|
||||
.name = "tee",
|
||||
.dev_groups = tee_dev_groups,
|
||||
};
|
||||
|
||||
/**
|
||||
* tee_device_register() - Registers a TEE device
|
||||
* @teedev: Device to register
|
||||
@ -1001,8 +1010,6 @@ int tee_device_register(struct tee_device *teedev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
teedev->dev.groups = tee_dev_groups;
|
||||
|
||||
rc = cdev_device_add(&teedev->cdev, &teedev->dev);
|
||||
if (rc) {
|
||||
dev_err(&teedev->dev,
|
||||
|
@ -11,18 +11,6 @@
|
||||
struct mmc_data;
|
||||
struct mmc_request;
|
||||
|
||||
enum mmc_blk_status {
|
||||
MMC_BLK_SUCCESS = 0,
|
||||
MMC_BLK_PARTIAL,
|
||||
MMC_BLK_CMD_ERR,
|
||||
MMC_BLK_RETRY,
|
||||
MMC_BLK_ABORT,
|
||||
MMC_BLK_DATA_ERR,
|
||||
MMC_BLK_ECC_ERR,
|
||||
MMC_BLK_NOMEDIUM,
|
||||
MMC_BLK_NEW_REQUEST,
|
||||
};
|
||||
|
||||
struct mmc_command {
|
||||
u32 opcode;
|
||||
u32 arg;
|
||||
|
@ -264,16 +264,6 @@ struct mmc_cqe_ops {
|
||||
void (*cqe_recovery_finish)(struct mmc_host *host);
|
||||
};
|
||||
|
||||
struct mmc_async_req {
|
||||
/* active mmc request */
|
||||
struct mmc_request *mrq;
|
||||
/*
|
||||
* Check error status of completed mmc request.
|
||||
* Returns 0 if success otherwise non zero.
|
||||
*/
|
||||
enum mmc_blk_status (*err_check)(struct mmc_card *, struct mmc_async_req *);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mmc_slot - MMC slot functions
|
||||
*
|
||||
@ -291,20 +281,6 @@ struct mmc_slot {
|
||||
void *handler_priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* mmc_context_info - synchronization details for mmc context
|
||||
* @is_done_rcv wake up reason was done request
|
||||
* @is_new_req wake up reason was new request
|
||||
* @is_waiting_last_req mmc context waiting for single running request
|
||||
* @wait wait queue
|
||||
*/
|
||||
struct mmc_context_info {
|
||||
bool is_done_rcv;
|
||||
bool is_new_req;
|
||||
bool is_waiting_last_req;
|
||||
wait_queue_head_t wait;
|
||||
};
|
||||
|
||||
struct regulator;
|
||||
struct mmc_pwrseq;
|
||||
|
||||
@ -672,7 +648,8 @@ static inline void mmc_debugfs_err_stats_inc(struct mmc_host *host,
|
||||
host->err_stats[stat] += 1;
|
||||
}
|
||||
|
||||
int mmc_sd_switch(struct mmc_card *card, int mode, int group, u8 value, u8 *resp);
|
||||
int mmc_sd_switch(struct mmc_card *card, bool mode, int group,
|
||||
u8 value, u8 *resp);
|
||||
int mmc_send_status(struct mmc_card *card, u32 *status);
|
||||
int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error);
|
||||
int mmc_send_abort_tuning(struct mmc_host *host, u32 opcode);
|
||||
|
123
include/linux/rpmb.h
Normal file
123
include/linux/rpmb.h
Normal file
@ -0,0 +1,123 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2015-2019 Intel Corp. All rights reserved
|
||||
* Copyright (C) 2021-2022 Linaro Ltd
|
||||
*/
|
||||
#ifndef __RPMB_H__
|
||||
#define __RPMB_H__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* enum rpmb_type - type of underlying storage technology
|
||||
*
|
||||
* @RPMB_TYPE_EMMC : emmc (JESD84-B50.1)
|
||||
* @RPMB_TYPE_UFS : UFS (JESD220)
|
||||
* @RPMB_TYPE_NVME : NVM Express
|
||||
*/
|
||||
enum rpmb_type {
|
||||
RPMB_TYPE_EMMC,
|
||||
RPMB_TYPE_UFS,
|
||||
RPMB_TYPE_NVME,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct rpmb_descr - RPMB description provided by the underlying block device
|
||||
*
|
||||
* @type : block device type
|
||||
* @route_frames : routes frames to and from the RPMB device
|
||||
* @dev_id : unique device identifier read from the hardware
|
||||
* @dev_id_len : length of unique device identifier
|
||||
* @reliable_wr_count: number of sectors that can be written in one access
|
||||
* @capacity : capacity of the device in units of 128K
|
||||
*
|
||||
* @dev_id is intended to be used as input when deriving the authenticaion key.
|
||||
*/
|
||||
struct rpmb_descr {
|
||||
enum rpmb_type type;
|
||||
int (*route_frames)(struct device *dev, u8 *req, unsigned int req_len,
|
||||
u8 *resp, unsigned int resp_len);
|
||||
u8 *dev_id;
|
||||
size_t dev_id_len;
|
||||
u16 reliable_wr_count;
|
||||
u16 capacity;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct rpmb_dev - device which can support RPMB partition
|
||||
*
|
||||
* @dev : device
|
||||
* @id : device_id
|
||||
* @list_node : linked list node
|
||||
* @descr : RPMB description
|
||||
*/
|
||||
struct rpmb_dev {
|
||||
struct device dev;
|
||||
int id;
|
||||
struct list_head list_node;
|
||||
struct rpmb_descr descr;
|
||||
};
|
||||
|
||||
#define to_rpmb_dev(x) container_of((x), struct rpmb_dev, dev)
|
||||
|
||||
#if IS_ENABLED(CONFIG_RPMB)
|
||||
struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev);
|
||||
void rpmb_dev_put(struct rpmb_dev *rdev);
|
||||
struct rpmb_dev *rpmb_dev_find_device(const void *data,
|
||||
const struct rpmb_dev *start,
|
||||
int (*match)(struct device *dev,
|
||||
const void *data));
|
||||
int rpmb_interface_register(struct class_interface *intf);
|
||||
void rpmb_interface_unregister(struct class_interface *intf);
|
||||
struct rpmb_dev *rpmb_dev_register(struct device *dev,
|
||||
struct rpmb_descr *descr);
|
||||
int rpmb_dev_unregister(struct rpmb_dev *rdev);
|
||||
|
||||
int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req,
|
||||
unsigned int req_len, u8 *resp, unsigned int resp_len);
|
||||
|
||||
#else
|
||||
static inline struct rpmb_dev *rpmb_dev_get(struct rpmb_dev *rdev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void rpmb_dev_put(struct rpmb_dev *rdev) { }
|
||||
|
||||
static inline struct rpmb_dev *
|
||||
rpmb_dev_find_device(const void *data, const struct rpmb_dev *start,
|
||||
int (*match)(struct device *dev, const void *data))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int rpmb_interface_register(struct class_interface *intf)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline void rpmb_interface_unregister(struct class_interface *intf)
|
||||
{
|
||||
}
|
||||
|
||||
static inline struct rpmb_dev *
|
||||
rpmb_dev_register(struct device *dev, struct rpmb_descr *descr)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int rpmb_dev_unregister(struct rpmb_dev *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int rpmb_route_frames(struct rpmb_dev *rdev, u8 *req,
|
||||
unsigned int req_len, u8 *resp,
|
||||
unsigned int resp_len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif /* CONFIG_RPMB */
|
||||
|
||||
#endif /* __RPMB_H__ */
|
@ -154,6 +154,18 @@ int tee_device_register(struct tee_device *teedev);
|
||||
*/
|
||||
void tee_device_unregister(struct tee_device *teedev);
|
||||
|
||||
/**
|
||||
* tee_device_set_dev_groups() - Set device attribute groups
|
||||
* @teedev: Device to register
|
||||
* @dev_groups: Attribute groups
|
||||
*
|
||||
* Assigns the provided @dev_groups to the @teedev to be registered later
|
||||
* with tee_device_register(). Calling this function is optional, but if
|
||||
* it's called it must be called before tee_device_register().
|
||||
*/
|
||||
void tee_device_set_dev_groups(struct tee_device *teedev,
|
||||
const struct attribute_group **dev_groups);
|
||||
|
||||
/**
|
||||
* tee_session_calc_client_uuid() - Calculates client UUID for session
|
||||
* @uuid: Resulting UUID
|
||||
|
Loading…
Reference in New Issue
Block a user