spi/regulator: Updates for v5.7
At one point in the release cycle I managed to fat finger things and apply some SPI fixes onto a regulator branch and merge that into the SPI tree, then pull in a change shared with the MTD tree moving the Mediatek quadspi driver over to become the Mediatek spi-nor driver in the SPI tree. This has made a mess which I only just noticed while preparing this and I can't see a sensible way to unpick things due to other subsequent merge commits especially the pull from MTD so it looks like the most sensible thing to do is give up and combine the two pull requests - I hope this is OK. Sorry about this, I've changed some bits of workflow which should hopefully help me spot such issues earlier in future. Fortunately both subsystems were fairly quiet this cycle, the highlights are: regulator: - Support for Monoloithic Power Systems MP5416, MP8867 and MPS8869 and Qualcomm PMI8994 and SMB208. SPI: - Lots of enhancements for spi-fsl-dspi, including XSPI mode support, from Vladimir Oltean. - Support for amlogic Meson G12A, IBM FSI, Mediatek spi-nor (moved from MTD), NXP i.MX8Mx, Rockchip PX30, RK3308 and RK3328, and Qualcomm Atheros AR934x/QCA95xx. -----BEGIN PGP SIGNATURE----- iQFHBAABCgAxFiEEreZoqmdXGLWf4p/qJNaLcl1Uh9AFAl6B5OUTHGJyb29uaWVA a2VybmVsLm9yZwAKCRAk1otyXVSH0F64B/wMN9AALiI+L8yLWbhlCebJLrtX4Nc5 2Pe25eJNnYRor+s8SxWK7rJ1IlMHQv/lkD2kCtjtmAvLXQwxyy2r8gW0hsZ8bmUS O1otrQ8wqfY8Enr8w/qXBMxR46U6vs1sfKDsAEPPUDt5voPbiDGpNazHytWU5Nnp yLh/8LdACFbanueeth5b14aYZNRVHb04Dh6yDjZOKu0zgNtBmkTQGNN8A4jJyVHC 0SFE0vo1nYsc2w02U6i9ko/GEA/lRSTNZZslXif3wvZQXvy0EgC8sQGLbCdxu8ES K2EBdeSCaMEoU+s2mFZ2Fuvd3RvGliHxWbn4SZtpaDp+/SBIu52kM4B+ =Eqwk -----END PGP SIGNATURE----- Merge tag 'regulator-spi-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/misc Pull spi and regulator updates from Mark Brown: "At one point in the release cycle I managed to fat finger things and apply some SPI fixes onto a regulator branch and merge that into the SPI tree, then pull in a change shared with the MTD tree moving the Mediatek quadspi driver over to become the Mediatek spi-nor driver in the SPI tree. This has made a mess which I only just noticed while preparing this and I can't see a sensible way to unpick things due to other subsequent merge commits especially the pull from MTD so it looks like the most sensible thing to do is give up and combine the two pull requests. Fortunately both subsystems were fairly quiet this cycle, the highlights are: regulator: - Support for Monoloithic Power Systems MP5416, MP8867 and MPS8869 and Qualcomm PMI8994 and SMB208. SPI: - Lots of enhancements for spi-fsl-dspi, including XSPI mode support, from Vladimir Oltean. - Support for amlogic Meson G12A, IBM FSI, Mediatek spi-nor (moved from MTD), NXP i.MX8Mx, Rockchip PX30, RK3308 and RK3328, and Qualcomm Atheros AR934x/QCA95xx" * tag 'regulator-spi-v5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/misc: (118 commits) spi: efm32: Convert to use GPIO descriptors regulator: qcom_smd: Add pmi8994 regulator support regulator: da9063: Fix get_mode() functions to read sleep field spi: spi-fsl-lpspi: Replace zero-length array with flexible-array member spi: spi-s3c24xx: Replace zero-length array with flexible-array member spi: stm32: Fix comments compilation warnings spi: atmel-quadspi: Add verbose debug facilities to monitor register accesses spi: spi-fsl-dspi: Add support for LS1028A spi: spi-fsl-dspi: Move invariant configs out of dspi_transfer_one_message spi: spi-fsl-dspi: Fix interrupt-less DMA mode taking an XSPI code path spi: spi-fsl-dspi: Avoid NULL pointer in dspi_slave_abort for non-DMA mode spi: spi-fsl-dspi: Replace interruptible wait queue with a simple completion spi: spi-fsl-dspi: Protect against races on dspi->words_in_flight spi: spi-fsl-dspi: Avoid reading more data than written in EOQ mode spi: spi-fsl-dspi: Fix bits-per-word acceleration in DMA mode spi: spi-fsl-dspi: Fix little endian access to PUSHR CMD and TXDATA spi: spi-fsl-dspi: Don't access reserved fields in SPI_MCR regulator: driver.h: fix regulator_map_* function names regulator: da9063: fix suspend spi: mxs: Drop GPIO includes ...
This commit is contained in:
commit
a231bed226
|
@ -0,0 +1,36 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-or-later)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/fsi/ibm,fsi2spi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: IBM FSI-attached SPI controllers
|
||||
|
||||
maintainers:
|
||||
- Eddie James <eajames@linux.ibm.com>
|
||||
|
||||
description: |
|
||||
This binding describes an FSI CFAM engine called the FSI2SPI. Therefore this
|
||||
node will always be a child of an FSI CFAM node; see fsi.txt for details on
|
||||
FSI slave and CFAM nodes. This FSI2SPI engine provides access to a number of
|
||||
SPI controllers.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ibm,fsi2spi
|
||||
|
||||
reg:
|
||||
items:
|
||||
- description: FSI slave address
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
examples:
|
||||
- |
|
||||
fsi2spi@1c00 {
|
||||
compatible = "ibm,fsi2spi";
|
||||
reg = <0x1c00 0x400>;
|
||||
};
|
|
@ -61,6 +61,7 @@ Regulator nodes are identified by their compatible:
|
|||
"qcom,rpm-pm8901-regulators"
|
||||
"qcom,rpm-pm8921-regulators"
|
||||
"qcom,rpm-pm8018-regulators"
|
||||
"qcom,rpm-smb208-regulators"
|
||||
|
||||
- vdd_l0_l1_lvs-supply:
|
||||
- vdd_l2_l11_l12-supply:
|
||||
|
@ -171,6 +172,9 @@ pm8018:
|
|||
s1, s2, s3, s4, s5, , l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11,
|
||||
l12, l14, lvs1
|
||||
|
||||
smb208:
|
||||
s1a, s1b, s2a, s2b
|
||||
|
||||
The content of each sub-node is defined by the standard binding for regulators -
|
||||
see regulator.txt - with additional custom properties described below:
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
Monolithic Power Systems MP8867/MP8869 voltage regulator
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be one of the following.
|
||||
"mps,mp8867"
|
||||
"mps,mp8869"
|
||||
- reg: I2C slave address.
|
||||
- enable-gpios: enable gpios.
|
||||
- mps,fb-voltage-divider: An array of two integers containing the resistor
|
||||
values R1 and R2 of the feedback voltage divider in kilo ohms.
|
||||
|
||||
Any property defined as part of the core regulator binding, defined in
|
||||
./regulator.txt, can also be used.
|
||||
|
||||
Example:
|
||||
|
||||
vcpu: regulator@62 {
|
||||
compatible = "mps,mp8869";
|
||||
regulator-name = "vcpu";
|
||||
regulator-min-microvolt = <700000>;
|
||||
regulator-max-microvolt = <850000>;
|
||||
regulator-always-on;
|
||||
regulator-boot-on;
|
||||
enable-gpios = <&porta 1 GPIO_ACTIVE_LOW>;
|
||||
mps,fb-voltage-divider = <80 240>;
|
||||
reg = <0x62>;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/regulator/mps,mp5416.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Monolithic Power System MP5416 PMIC
|
||||
|
||||
maintainers:
|
||||
- Saravanan Sekar <sravanhome@gmail.com>
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
pattern: "^pmic@[0-9a-f]{1,2}$"
|
||||
compatible:
|
||||
enum:
|
||||
- mps,mp5416
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
regulators:
|
||||
type: object
|
||||
description: |
|
||||
list of regulators provided by this controller, must be named
|
||||
after their hardware counterparts BUCK[1-4] and LDO[1-4]
|
||||
|
||||
patternProperties:
|
||||
"^buck[1-4]$":
|
||||
allOf:
|
||||
- $ref: "regulator.yaml#"
|
||||
type: object
|
||||
|
||||
"^ldo[1-4]$":
|
||||
allOf:
|
||||
- $ref: "regulator.yaml#"
|
||||
type: object
|
||||
|
||||
additionalProperties: false
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- regulators
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
pmic@69 {
|
||||
compatible = "mps,mp5416";
|
||||
reg = <0x69>;
|
||||
|
||||
regulators {
|
||||
|
||||
buck1 {
|
||||
regulator-name = "buck1";
|
||||
regulator-min-microvolt = <600000>;
|
||||
regulator-max-microvolt = <2187500>;
|
||||
regulator-min-microamp = <3800000>;
|
||||
regulator-max-microamp = <6800000>;
|
||||
regulator-boot-on;
|
||||
};
|
||||
|
||||
ldo2 {
|
||||
regulator-name = "ldo2";
|
||||
regulator-min-microvolt = <800000>;
|
||||
regulator-max-microvolt = <3975000>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -26,6 +26,7 @@ Regulator nodes are identified by their compatible:
|
|||
"qcom,rpm-pm8994-regulators"
|
||||
"qcom,rpm-pm8998-regulators"
|
||||
"qcom,rpm-pma8084-regulators"
|
||||
"qcom,rpm-pmi8994-regulators"
|
||||
"qcom,rpm-pmi8998-regulators"
|
||||
"qcom,rpm-pms405-regulators"
|
||||
|
||||
|
@ -143,6 +144,15 @@ Regulator nodes are identified by their compatible:
|
|||
Definition: reference to regulator supplying the input pin, as
|
||||
described in the data sheet
|
||||
|
||||
- vdd_s1-supply:
|
||||
- vdd_s2-supply:
|
||||
- vdd_s3-supply:
|
||||
- vdd_bst_byp-supply:
|
||||
Usage: optional (pmi8994 only)
|
||||
Value type: <phandle>
|
||||
Definition: reference to regulator supplying the input pin, as
|
||||
described in the data sheet
|
||||
|
||||
- vdd_s1-supply:
|
||||
- vdd_s2-supply:
|
||||
- vdd_s3-supply:
|
||||
|
@ -259,6 +269,9 @@ pma8084:
|
|||
l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20,
|
||||
l21, l22, l23, l24, l25, l26, l27, lvs1, lvs2, lvs3, lvs4, 5vs1
|
||||
|
||||
pmi8994:
|
||||
s1, s2, s3, boost-bypass
|
||||
|
||||
pmi8998:
|
||||
bob
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/regulator/vqmmc-ipq4019-regulator.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm IPQ4019 VQMMC SD LDO regulator
|
||||
|
||||
maintainers:
|
||||
- Robert Marko <robert.marko@sartura.hr>
|
||||
|
||||
description: |
|
||||
Qualcomm IPQ4019 SoC-s feature a built a build SD/EMMC controller,
|
||||
in order to support both 1.8 and 3V I/O voltage levels an LDO
|
||||
controller is also embedded.
|
||||
|
||||
allOf:
|
||||
- $ref: "regulator.yaml#"
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: qcom,vqmmc-ipq4019-regulator
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
examples:
|
||||
- |
|
||||
regulator@1948000 {
|
||||
compatible = "qcom,vqmmc-ipq4019-regulator";
|
||||
reg = <0x01948000 0x4>;
|
||||
regulator-name = "vqmmc";
|
||||
regulator-min-microvolt = <1500000>;
|
||||
regulator-max-microvolt = <3000000>;
|
||||
regulator-always-on;
|
||||
status = "disabled";
|
||||
};
|
||||
...
|
|
@ -22,6 +22,7 @@ properties:
|
|||
enum:
|
||||
- amlogic,meson-gx-spicc # SPICC controller on Amlogic GX and compatible SoCs
|
||||
- amlogic,meson-axg-spicc # SPICC controller on Amlogic AXG and compatible SoCs
|
||||
- amlogic,meson-g12a-spicc # SPICC controller on Amlogic G12A and compatible SoCs
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
@ -40,6 +41,27 @@ properties:
|
|||
items:
|
||||
- const: core
|
||||
|
||||
if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- amlogic,meson-g12a-spicc
|
||||
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
contains:
|
||||
items:
|
||||
- description: controller register bus clock
|
||||
- description: baud rate generator and delay control clock
|
||||
|
||||
clock-names:
|
||||
minItems: 2
|
||||
items:
|
||||
- const: core
|
||||
- const: pclk
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
|
|
@ -10,7 +10,10 @@ Required properties:
|
|||
- "fsl,imx35-cspi" for SPI compatible with the one integrated on i.MX35
|
||||
- "fsl,imx51-ecspi" for SPI compatible with the one integrated on i.MX51
|
||||
- "fsl,imx53-ecspi" for SPI compatible with the one integrated on i.MX53 and later Soc
|
||||
- "fsl,imx8mq-ecspi" for SPI compatible with the one integrated on i.MX8M
|
||||
- "fsl,imx8mq-ecspi" for SPI compatible with the one integrated on i.MX8MQ
|
||||
- "fsl,imx8mm-ecspi" for SPI compatible with the one integrated on i.MX8MM
|
||||
- "fsl,imx8mn-ecspi" for SPI compatible with the one integrated on i.MX8MN
|
||||
- "fsl,imx8mp-ecspi" for SPI compatible with the one integrated on i.MX8MP
|
||||
- reg : Offset and length of the register set for the device
|
||||
- interrupts : Should contain CSPI/eCSPI interrupt
|
||||
- clocks : Clock specifiers for both ipg and per clocks.
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/qca,ar934x-spi.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm Atheros AR934x/QCA95xx SoC SPI controller
|
||||
|
||||
maintainers:
|
||||
- Chuanhong Guo <gch981213@gmail.com>
|
||||
|
||||
allOf:
|
||||
- $ref: spi-controller.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: qca,ar934x-spi
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- clocks
|
||||
- '#address-cells'
|
||||
- '#size-cells'
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/ath79-clk.h>
|
||||
spi: spi@1f000000 {
|
||||
compatible = "qca,ar934x-spi";
|
||||
reg = <0x1f000000 0x1c>;
|
||||
clocks = <&pll ATH79_CLK_AHB>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
|
@ -52,6 +52,12 @@ properties:
|
|||
description:
|
||||
The SPI controller acts as a slave, instead of a master.
|
||||
|
||||
oneOf:
|
||||
- required:
|
||||
- "#address-cells"
|
||||
- required:
|
||||
- spi-slave
|
||||
|
||||
patternProperties:
|
||||
"^slave$":
|
||||
type: object
|
||||
|
@ -114,7 +120,7 @@ patternProperties:
|
|||
- enum: [ 1, 2, 4, 8 ]
|
||||
- default: 1
|
||||
description:
|
||||
Bus width to the SPI bus used for MISO.
|
||||
Bus width to the SPI bus used for read transfers.
|
||||
|
||||
spi-rx-delay-us:
|
||||
description:
|
||||
|
@ -126,7 +132,7 @@ patternProperties:
|
|||
- enum: [ 1, 2, 4, 8 ]
|
||||
- default: 1
|
||||
description:
|
||||
Bus width to the SPI bus used for MOSI.
|
||||
Bus width to the SPI bus used for write transfers.
|
||||
|
||||
spi-tx-delay-us:
|
||||
description:
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
ARM Freescale DSPI controller
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,vf610-dspi", "fsl,ls1021a-v1.0-dspi",
|
||||
"fsl,ls2085a-dspi"
|
||||
or
|
||||
"fsl,ls2080a-dspi" followed by "fsl,ls2085a-dspi"
|
||||
"fsl,ls1012a-dspi" followed by "fsl,ls1021a-v1.0-dspi"
|
||||
"fsl,ls1088a-dspi" followed by "fsl,ls1021a-v1.0-dspi"
|
||||
- compatible : must be one of:
|
||||
"fsl,vf610-dspi",
|
||||
"fsl,ls1021a-v1.0-dspi",
|
||||
"fsl,ls1012a-dspi" (optionally followed by "fsl,ls1021a-v1.0-dspi"),
|
||||
"fsl,ls1028a-dspi",
|
||||
"fsl,ls1043a-dspi" (optionally followed by "fsl,ls1021a-v1.0-dspi"),
|
||||
"fsl,ls1046a-dspi" (optionally followed by "fsl,ls1021a-v1.0-dspi"),
|
||||
"fsl,ls1088a-dspi" (optionally followed by "fsl,ls1021a-v1.0-dspi"),
|
||||
"fsl,ls2080a-dspi" (optionally followed by "fsl,ls2085a-dspi"),
|
||||
"fsl,ls2085a-dspi",
|
||||
"fsl,lx2160a-dspi",
|
||||
- reg : Offset and length of the register set for the device
|
||||
- interrupts : Should contain SPI controller interrupt
|
||||
- clocks: from common clock binding: handle to dspi clock.
|
||||
|
@ -14,11 +19,11 @@ Required properties:
|
|||
- pinctrl-0: pin control group to be used for this controller.
|
||||
- pinctrl-names: must contain a "default" entry.
|
||||
- spi-num-chipselects : the number of the chipselect signals.
|
||||
- bus-num : the slave chip chipselect signal number.
|
||||
|
||||
Optional property:
|
||||
- big-endian: If present the dspi device's registers are implemented
|
||||
in big endian mode.
|
||||
- bus-num : the slave chip chipselect signal number.
|
||||
|
||||
Optional SPI slave node properties:
|
||||
- fsl,spi-cs-sck-delay: a delay in nanoseconds between activating chip
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
* Serial NOR flash controller for MediaTek SoCs
|
||||
* Serial NOR flash controller for MediaTek ARM SoCs
|
||||
|
||||
Required properties:
|
||||
- compatible: For mt8173, compatible should be "mediatek,mt8173-nor",
|
||||
|
@ -13,6 +13,7 @@ Required properties:
|
|||
"mediatek,mt7629-nor", "mediatek,mt8173-nor"
|
||||
"mediatek,mt8173-nor"
|
||||
- reg: physical base address and length of the controller's register
|
||||
- interrupts: Interrupt number used by the controller.
|
||||
- clocks: the phandle of the clocks needed by the nor controller
|
||||
- clock-names: the names of the clocks
|
||||
the clocks should be named "spi" and "sf". "spi" is used for spi bus,
|
||||
|
@ -22,20 +23,16 @@ Required properties:
|
|||
- #address-cells: should be <1>
|
||||
- #size-cells: should be <0>
|
||||
|
||||
The SPI flash must be a child of the nor_flash node and must have a
|
||||
compatible property. Also see jedec,spi-nor.txt.
|
||||
|
||||
Required properties:
|
||||
- compatible: May include a device-specific string consisting of the manufacturer
|
||||
and name of the chip. Must also include "jedec,spi-nor" for any
|
||||
SPI NOR flash that can be identified by the JEDEC READ ID opcode (0x9F).
|
||||
- reg : Chip-Select number
|
||||
There should be only one spi slave device following generic spi bindings.
|
||||
It's not recommended to use this controller for devices other than SPI NOR
|
||||
flash due to limited transfer capability of this controller.
|
||||
|
||||
Example:
|
||||
|
||||
nor_flash: spi@1100d000 {
|
||||
compatible = "mediatek,mt8173-nor";
|
||||
reg = <0 0x1100d000 0 0xe0>;
|
||||
interrupts = <&spi_flash_irq>;
|
||||
clocks = <&pericfg CLK_PERI_SPI>,
|
||||
<&topckgen CLK_TOP_SPINFI_IFR_SEL>;
|
||||
clock-names = "spi", "sf";
|
|
@ -0,0 +1,89 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/spi-mux.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Generic SPI Multiplexer
|
||||
|
||||
description: |
|
||||
This binding describes a SPI bus multiplexer to route the SPI chip select
|
||||
signals. This can be used when you need more devices than the SPI controller
|
||||
has chip selects available. An example setup is shown in ASCII art; the actual
|
||||
setting of the multiplexer to a channel needs to be done by a specific SPI mux
|
||||
driver.
|
||||
|
||||
MOSI /--------------------------------+--------+--------+--------\
|
||||
MISO |/------------------------------+|-------+|-------+|-------\|
|
||||
SCL ||/----------------------------+||------+||------+||------\||
|
||||
||| ||| ||| ||| |||
|
||||
+------------+ ||| ||| ||| |||
|
||||
| SoC ||| | +-+++-+ +-+++-+ +-+++-+ +-+++-+
|
||||
| ||| | | dev | | dev | | dev | | dev |
|
||||
| +--+++-+ | CS-X +------+\ +--+--+ +--+--+ +--+--+ +--+--+
|
||||
| | SPI +-|-------+ Mux |\\ CS-0 | | | |
|
||||
| +------+ | +--+---+\\\-------/ CS-1 | | |
|
||||
| | | \\\----------------/ CS-2 | |
|
||||
| +------+ | | \\-------------------------/ CS-3 |
|
||||
| | ? +-|----------/ \----------------------------------/
|
||||
| +------+ |
|
||||
+------------+
|
||||
|
||||
allOf:
|
||||
- $ref: "/schemas/spi/spi-controller.yaml#"
|
||||
|
||||
maintainers:
|
||||
- Chris Packham <chris.packham@alliedtelesis.co.nz>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: spi-mux
|
||||
|
||||
mux-controls:
|
||||
maxItems: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- spi-max-frequency
|
||||
- mux-controls
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
mux: mux-controller {
|
||||
compatible = "gpio-mux";
|
||||
#mux-control-cells = <0>;
|
||||
|
||||
mux-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi@0 {
|
||||
compatible = "spi-mux";
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi-max-frequency = <100000000>;
|
||||
|
||||
mux-controls = <&mux>;
|
||||
|
||||
spi-flash@0 {
|
||||
compatible = "jedec,spi-nor";
|
||||
reg = <0>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi-max-frequency = <40000000>;
|
||||
};
|
||||
|
||||
spi-device@1 {
|
||||
compatible = "lineartechnology,ltc2488";
|
||||
reg = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
spi-max-frequency = <10000000>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
Required properties:
|
||||
- compatible : Should be "nxp,lx2160a-fspi"
|
||||
"nxp,imx8qxp-fspi"
|
||||
"nxp,imx8mm-fspi"
|
||||
|
||||
- reg : First contains the register location and length,
|
||||
Second contains the memory mapping address and length
|
||||
- reg-names : Should contain the resource reg names:
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
* Rockchip SPI Controller
|
||||
|
||||
The Rockchip SPI controller is used to interface with various devices such as flash
|
||||
and display controllers using the SPI communication interface.
|
||||
|
||||
Required Properties:
|
||||
|
||||
- compatible: should be one of the following.
|
||||
"rockchip,rv1108-spi" for rv1108 SoCs.
|
||||
"rockchip,px30-spi", "rockchip,rk3066-spi" for px30 SoCs.
|
||||
"rockchip,rk3036-spi" for rk3036 SoCS.
|
||||
"rockchip,rk3066-spi" for rk3066 SoCs.
|
||||
"rockchip,rk3188-spi" for rk3188 SoCs.
|
||||
"rockchip,rk3228-spi" for rk3228 SoCS.
|
||||
"rockchip,rk3288-spi" for rk3288 SoCs.
|
||||
"rockchip,rk3368-spi" for rk3368 SoCs.
|
||||
"rockchip,rk3399-spi" for rk3399 SoCs.
|
||||
- reg: physical base address of the controller and length of memory mapped
|
||||
region.
|
||||
- interrupts: The interrupt number to the cpu. The interrupt specifier format
|
||||
depends on the interrupt controller.
|
||||
- clocks: Must contain an entry for each entry in clock-names.
|
||||
- clock-names: Shall be "spiclk" for the transfer-clock, and "apb_pclk" for
|
||||
the peripheral clock.
|
||||
- #address-cells: should be 1.
|
||||
- #size-cells: should be 0.
|
||||
|
||||
Optional Properties:
|
||||
|
||||
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding,
|
||||
Documentation/devicetree/bindings/dma/dma.txt
|
||||
- dma-names: DMA request names should include "tx" and "rx" if present.
|
||||
- rx-sample-delay-ns: nanoseconds to delay after the SCLK edge before sampling
|
||||
Rx data (may need to be fine tuned for high capacitance lines).
|
||||
No delay (0) by default.
|
||||
- pinctrl-names: Names for the pin configuration(s); may be "default" or
|
||||
"sleep", where the "sleep" configuration may describe the state
|
||||
the pins should be in during system suspend. See also
|
||||
pinctrl/pinctrl-bindings.txt.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
spi0: spi@ff110000 {
|
||||
compatible = "rockchip,rk3066-spi";
|
||||
reg = <0xff110000 0x1000>;
|
||||
dmas = <&pdma1 11>, <&pdma1 12>;
|
||||
dma-names = "tx", "rx";
|
||||
rx-sample-delay-ns = <10>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
|
||||
clock-names = "spiclk", "apb_pclk";
|
||||
pinctrl-0 = <&spi1_pins>;
|
||||
pinctrl-1 = <&spi1_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
|
@ -0,0 +1,107 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/spi/spi-rockchip.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Rockchip SPI Controller
|
||||
|
||||
description:
|
||||
The Rockchip SPI controller is used to interface with various devices such
|
||||
as flash and display controllers using the SPI communication interface.
|
||||
|
||||
allOf:
|
||||
- $ref: "spi-controller.yaml#"
|
||||
|
||||
maintainers:
|
||||
- Heiko Stuebner <heiko@sntech.de>
|
||||
|
||||
# Everything else is described in the common file
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- const: rockchip,rk3036-spi
|
||||
- const: rockchip,rk3066-spi
|
||||
- const: rockchip,rk3228-spi
|
||||
- const: rockchip,rv1108-spi
|
||||
- items:
|
||||
- enum:
|
||||
- rockchip,px30-spi
|
||||
- rockchip,rk3188-spi
|
||||
- rockchip,rk3288-spi
|
||||
- rockchip,rk3308-spi
|
||||
- rockchip,rk3328-spi
|
||||
- rockchip,rk3368-spi
|
||||
- rockchip,rk3399-spi
|
||||
- const: rockchip,rk3066-spi
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
items:
|
||||
- description: transfer-clock
|
||||
- description: peripheral clock
|
||||
|
||||
clock-names:
|
||||
items:
|
||||
- const: spiclk
|
||||
- const: apb_pclk
|
||||
|
||||
dmas:
|
||||
items:
|
||||
- description: TX DMA Channel
|
||||
- description: RX DMA Channel
|
||||
|
||||
dma-names:
|
||||
items:
|
||||
- const: tx
|
||||
- const: rx
|
||||
|
||||
rx-sample-delay-ns:
|
||||
default: 0
|
||||
description:
|
||||
Nano seconds to delay after the SCLK edge before sampling Rx data
|
||||
(may need to be fine tuned for high capacitance lines).
|
||||
If not specified 0 will be used.
|
||||
|
||||
pinctrl-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: default
|
||||
- const: sleep
|
||||
description:
|
||||
Names for the pin configuration(s); may be "default" or "sleep",
|
||||
where the "sleep" configuration may describe the state
|
||||
the pins should be in during system suspend.
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- interrupts
|
||||
- clocks
|
||||
- clock-names
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/rk3188-cru-common.h>
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
spi0: spi@ff110000 {
|
||||
compatible = "rockchip,rk3066-spi";
|
||||
reg = <0xff110000 0x1000>;
|
||||
interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>;
|
||||
clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
|
||||
clock-names = "spiclk", "apb_pclk";
|
||||
dmas = <&pdma1 11>, <&pdma1 12>;
|
||||
dma-names = "tx", "rx";
|
||||
pinctrl-0 = <&spi1_pins>;
|
||||
pinctrl-1 = <&spi1_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
rx-sample-delay-ns = <10>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
11
MAINTAINERS
11
MAINTAINERS
|
@ -2293,6 +2293,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip.git
|
|||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/i2c/i2c-rk3x.txt
|
||||
F: Documentation/devicetree/bindings/mmc/rockchip-dw-mshc.yaml
|
||||
F: Documentation/devicetree/bindings/spi/spi-rockchip.yaml
|
||||
F: arch/arm/boot/dts/rk3*
|
||||
F: arch/arm/boot/dts/rv1108*
|
||||
F: arch/arm/mach-rockchip/
|
||||
|
@ -6878,6 +6879,13 @@ S: Maintained
|
|||
F: drivers/i2c/busses/i2c-fsi.c
|
||||
F: Documentation/devicetree/bindings/i2c/i2c-fsi.txt
|
||||
|
||||
FSI-ATTACHED SPI DRIVER
|
||||
M: Eddie James <eajames@linux.ibm.com>
|
||||
L: linux-spi@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/spi/spi-fsi.c
|
||||
F: Documentation/devicetree/bindings/fsi/ibm,fsi2spi.yaml
|
||||
|
||||
FSNOTIFY: FILESYSTEM NOTIFICATION INFRASTRUCTURE
|
||||
M: Jan Kara <jack@suse.cz>
|
||||
R: Amir Goldstein <amir73il@gmail.com>
|
||||
|
@ -11283,7 +11291,8 @@ F: drivers/tty/mxser.*
|
|||
MONOLITHIC POWER SYSTEM PMIC DRIVER
|
||||
M: Saravanan Sekar <sravanhome@gmail.com>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/regulator/mpq7920.yaml
|
||||
F: Documentation/devicetree/bindings/regulator/mps,mp*.yaml
|
||||
F: drivers/regulator/mp5416.c
|
||||
F: drivers/regulator/mpq7920.c
|
||||
F: drivers/regulator/mpq7920.h
|
||||
|
||||
|
|
|
@ -52,14 +52,6 @@ config SPI_HISI_SFC
|
|||
help
|
||||
This enables support for HiSilicon FMC SPI-NOR flash controller.
|
||||
|
||||
config SPI_MTK_QUADSPI
|
||||
tristate "MediaTek Quad SPI controller"
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for the Quad SPI controller in master mode.
|
||||
This controller does not support generic SPI. It only supports
|
||||
SPI NOR.
|
||||
|
||||
config SPI_NXP_SPIFI
|
||||
tristate "NXP SPI Flash Interface (SPIFI)"
|
||||
depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
|
||||
|
|
|
@ -3,7 +3,6 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
|||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
obj-$(CONFIG_SPI_MTK_QUADSPI) += mtk-quadspi.o
|
||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PCI) += intel-spi-pci.o
|
||||
|
|
|
@ -1,565 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2015 MediaTek Inc.
|
||||
* Author: Bayi Cheng <bayi.cheng@mediatek.com>
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#define MTK_NOR_CMD_REG 0x00
|
||||
#define MTK_NOR_CNT_REG 0x04
|
||||
#define MTK_NOR_RDSR_REG 0x08
|
||||
#define MTK_NOR_RDATA_REG 0x0c
|
||||
#define MTK_NOR_RADR0_REG 0x10
|
||||
#define MTK_NOR_RADR1_REG 0x14
|
||||
#define MTK_NOR_RADR2_REG 0x18
|
||||
#define MTK_NOR_WDATA_REG 0x1c
|
||||
#define MTK_NOR_PRGDATA0_REG 0x20
|
||||
#define MTK_NOR_PRGDATA1_REG 0x24
|
||||
#define MTK_NOR_PRGDATA2_REG 0x28
|
||||
#define MTK_NOR_PRGDATA3_REG 0x2c
|
||||
#define MTK_NOR_PRGDATA4_REG 0x30
|
||||
#define MTK_NOR_PRGDATA5_REG 0x34
|
||||
#define MTK_NOR_SHREG0_REG 0x38
|
||||
#define MTK_NOR_SHREG1_REG 0x3c
|
||||
#define MTK_NOR_SHREG2_REG 0x40
|
||||
#define MTK_NOR_SHREG3_REG 0x44
|
||||
#define MTK_NOR_SHREG4_REG 0x48
|
||||
#define MTK_NOR_SHREG5_REG 0x4c
|
||||
#define MTK_NOR_SHREG6_REG 0x50
|
||||
#define MTK_NOR_SHREG7_REG 0x54
|
||||
#define MTK_NOR_SHREG8_REG 0x58
|
||||
#define MTK_NOR_SHREG9_REG 0x5c
|
||||
#define MTK_NOR_CFG1_REG 0x60
|
||||
#define MTK_NOR_CFG2_REG 0x64
|
||||
#define MTK_NOR_CFG3_REG 0x68
|
||||
#define MTK_NOR_STATUS0_REG 0x70
|
||||
#define MTK_NOR_STATUS1_REG 0x74
|
||||
#define MTK_NOR_STATUS2_REG 0x78
|
||||
#define MTK_NOR_STATUS3_REG 0x7c
|
||||
#define MTK_NOR_FLHCFG_REG 0x84
|
||||
#define MTK_NOR_TIME_REG 0x94
|
||||
#define MTK_NOR_PP_DATA_REG 0x98
|
||||
#define MTK_NOR_PREBUF_STUS_REG 0x9c
|
||||
#define MTK_NOR_DELSEL0_REG 0xa0
|
||||
#define MTK_NOR_DELSEL1_REG 0xa4
|
||||
#define MTK_NOR_INTRSTUS_REG 0xa8
|
||||
#define MTK_NOR_INTREN_REG 0xac
|
||||
#define MTK_NOR_CHKSUM_CTL_REG 0xb8
|
||||
#define MTK_NOR_CHKSUM_REG 0xbc
|
||||
#define MTK_NOR_CMD2_REG 0xc0
|
||||
#define MTK_NOR_WRPROT_REG 0xc4
|
||||
#define MTK_NOR_RADR3_REG 0xc8
|
||||
#define MTK_NOR_DUAL_REG 0xcc
|
||||
#define MTK_NOR_DELSEL2_REG 0xd0
|
||||
#define MTK_NOR_DELSEL3_REG 0xd4
|
||||
#define MTK_NOR_DELSEL4_REG 0xd8
|
||||
|
||||
/* commands for mtk nor controller */
|
||||
#define MTK_NOR_READ_CMD 0x0
|
||||
#define MTK_NOR_RDSR_CMD 0x2
|
||||
#define MTK_NOR_PRG_CMD 0x4
|
||||
#define MTK_NOR_WR_CMD 0x10
|
||||
#define MTK_NOR_PIO_WR_CMD 0x90
|
||||
#define MTK_NOR_WRSR_CMD 0x20
|
||||
#define MTK_NOR_PIO_READ_CMD 0x81
|
||||
#define MTK_NOR_WR_BUF_ENABLE 0x1
|
||||
#define MTK_NOR_WR_BUF_DISABLE 0x0
|
||||
#define MTK_NOR_ENABLE_SF_CMD 0x30
|
||||
#define MTK_NOR_DUAD_ADDR_EN 0x8
|
||||
#define MTK_NOR_QUAD_READ_EN 0x4
|
||||
#define MTK_NOR_DUAL_ADDR_EN 0x2
|
||||
#define MTK_NOR_DUAL_READ_EN 0x1
|
||||
#define MTK_NOR_DUAL_DISABLE 0x0
|
||||
#define MTK_NOR_FAST_READ 0x1
|
||||
|
||||
#define SFLASH_WRBUF_SIZE 128
|
||||
|
||||
/* Can shift up to 48 bits (6 bytes) of TX/RX */
|
||||
#define MTK_NOR_MAX_RX_TX_SHIFT 6
|
||||
/* can shift up to 56 bits (7 bytes) transfer by MTK_NOR_PRG_CMD */
|
||||
#define MTK_NOR_MAX_SHIFT 7
|
||||
/* nor controller 4-byte address mode enable bit */
|
||||
#define MTK_NOR_4B_ADDR_EN BIT(4)
|
||||
|
||||
/* Helpers for accessing the program data / shift data registers */
|
||||
#define MTK_NOR_PRG_REG(n) (MTK_NOR_PRGDATA0_REG + 4 * (n))
|
||||
#define MTK_NOR_SHREG(n) (MTK_NOR_SHREG0_REG + 4 * (n))
|
||||
|
||||
struct mtk_nor {
|
||||
struct spi_nor nor;
|
||||
struct device *dev;
|
||||
void __iomem *base; /* nor flash base address */
|
||||
struct clk *spi_clk;
|
||||
struct clk *nor_clk;
|
||||
};
|
||||
|
||||
static void mtk_nor_set_read_mode(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
struct spi_nor *nor = &mtk_nor->nor;
|
||||
|
||||
switch (nor->read_proto) {
|
||||
case SNOR_PROTO_1_1_1:
|
||||
writeb(nor->read_opcode, mtk_nor->base +
|
||||
MTK_NOR_PRGDATA3_REG);
|
||||
writeb(MTK_NOR_FAST_READ, mtk_nor->base +
|
||||
MTK_NOR_CFG1_REG);
|
||||
break;
|
||||
case SNOR_PROTO_1_1_2:
|
||||
writeb(nor->read_opcode, mtk_nor->base +
|
||||
MTK_NOR_PRGDATA3_REG);
|
||||
writeb(MTK_NOR_DUAL_READ_EN, mtk_nor->base +
|
||||
MTK_NOR_DUAL_REG);
|
||||
break;
|
||||
case SNOR_PROTO_1_1_4:
|
||||
writeb(nor->read_opcode, mtk_nor->base +
|
||||
MTK_NOR_PRGDATA4_REG);
|
||||
writeb(MTK_NOR_QUAD_READ_EN, mtk_nor->base +
|
||||
MTK_NOR_DUAL_REG);
|
||||
break;
|
||||
default:
|
||||
writeb(MTK_NOR_DUAL_DISABLE, mtk_nor->base +
|
||||
MTK_NOR_DUAL_REG);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int mtk_nor_execute_cmd(struct mtk_nor *mtk_nor, u8 cmdval)
|
||||
{
|
||||
int reg;
|
||||
u8 val = cmdval & 0x1f;
|
||||
|
||||
writeb(cmdval, mtk_nor->base + MTK_NOR_CMD_REG);
|
||||
return readl_poll_timeout(mtk_nor->base + MTK_NOR_CMD_REG, reg,
|
||||
!(reg & val), 100, 10000);
|
||||
}
|
||||
|
||||
static int mtk_nor_do_tx_rx(struct mtk_nor *mtk_nor, u8 op,
|
||||
const u8 *tx, size_t txlen, u8 *rx, size_t rxlen)
|
||||
{
|
||||
size_t len = 1 + txlen + rxlen;
|
||||
int i, ret, idx;
|
||||
|
||||
if (len > MTK_NOR_MAX_SHIFT)
|
||||
return -EINVAL;
|
||||
|
||||
writeb(len * 8, mtk_nor->base + MTK_NOR_CNT_REG);
|
||||
|
||||
/* start at PRGDATA5, go down to PRGDATA0 */
|
||||
idx = MTK_NOR_MAX_RX_TX_SHIFT - 1;
|
||||
|
||||
/* opcode */
|
||||
writeb(op, mtk_nor->base + MTK_NOR_PRG_REG(idx));
|
||||
idx--;
|
||||
|
||||
/* program TX data */
|
||||
for (i = 0; i < txlen; i++, idx--)
|
||||
writeb(tx[i], mtk_nor->base + MTK_NOR_PRG_REG(idx));
|
||||
|
||||
/* clear out rest of TX registers */
|
||||
while (idx >= 0) {
|
||||
writeb(0, mtk_nor->base + MTK_NOR_PRG_REG(idx));
|
||||
idx--;
|
||||
}
|
||||
|
||||
ret = mtk_nor_execute_cmd(mtk_nor, MTK_NOR_PRG_CMD);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* restart at first RX byte */
|
||||
idx = rxlen - 1;
|
||||
|
||||
/* read out RX data */
|
||||
for (i = 0; i < rxlen; i++, idx--)
|
||||
rx[i] = readb(mtk_nor->base + MTK_NOR_SHREG(idx));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Do a WRSR (Write Status Register) command */
|
||||
static int mtk_nor_wr_sr(struct mtk_nor *mtk_nor, const u8 sr)
|
||||
{
|
||||
writeb(sr, mtk_nor->base + MTK_NOR_PRGDATA5_REG);
|
||||
writeb(8, mtk_nor->base + MTK_NOR_CNT_REG);
|
||||
return mtk_nor_execute_cmd(mtk_nor, MTK_NOR_WRSR_CMD);
|
||||
}
|
||||
|
||||
static int mtk_nor_write_buffer_enable(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
/* the bit0 of MTK_NOR_CFG2_REG is pre-fetch buffer
|
||||
* 0: pre-fetch buffer use for read
|
||||
* 1: pre-fetch buffer use for page program
|
||||
*/
|
||||
writel(MTK_NOR_WR_BUF_ENABLE, mtk_nor->base + MTK_NOR_CFG2_REG);
|
||||
return readb_poll_timeout(mtk_nor->base + MTK_NOR_CFG2_REG, reg,
|
||||
0x01 == (reg & 0x01), 100, 10000);
|
||||
}
|
||||
|
||||
static int mtk_nor_write_buffer_disable(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
writel(MTK_NOR_WR_BUF_DISABLE, mtk_nor->base + MTK_NOR_CFG2_REG);
|
||||
return readb_poll_timeout(mtk_nor->base + MTK_NOR_CFG2_REG, reg,
|
||||
MTK_NOR_WR_BUF_DISABLE == (reg & 0x1), 100,
|
||||
10000);
|
||||
}
|
||||
|
||||
static void mtk_nor_set_addr_width(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
u8 val;
|
||||
struct spi_nor *nor = &mtk_nor->nor;
|
||||
|
||||
val = readb(mtk_nor->base + MTK_NOR_DUAL_REG);
|
||||
|
||||
switch (nor->addr_width) {
|
||||
case 3:
|
||||
val &= ~MTK_NOR_4B_ADDR_EN;
|
||||
break;
|
||||
case 4:
|
||||
val |= MTK_NOR_4B_ADDR_EN;
|
||||
break;
|
||||
default:
|
||||
dev_warn(mtk_nor->dev, "Unexpected address width %u.\n",
|
||||
nor->addr_width);
|
||||
break;
|
||||
}
|
||||
|
||||
writeb(val, mtk_nor->base + MTK_NOR_DUAL_REG);
|
||||
}
|
||||
|
||||
static void mtk_nor_set_addr(struct mtk_nor *mtk_nor, u32 addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
mtk_nor_set_addr_width(mtk_nor);
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
writeb(addr & 0xff, mtk_nor->base + MTK_NOR_RADR0_REG + i * 4);
|
||||
addr >>= 8;
|
||||
}
|
||||
/* Last register is non-contiguous */
|
||||
writeb(addr & 0xff, mtk_nor->base + MTK_NOR_RADR3_REG);
|
||||
}
|
||||
|
||||
static ssize_t mtk_nor_read(struct spi_nor *nor, loff_t from, size_t length,
|
||||
u_char *buffer)
|
||||
{
|
||||
int i, ret;
|
||||
int addr = (int)from;
|
||||
u8 *buf = (u8 *)buffer;
|
||||
struct mtk_nor *mtk_nor = nor->priv;
|
||||
|
||||
/* set mode for fast read mode ,dual mode or quad mode */
|
||||
mtk_nor_set_read_mode(mtk_nor);
|
||||
mtk_nor_set_addr(mtk_nor, addr);
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
ret = mtk_nor_execute_cmd(mtk_nor, MTK_NOR_PIO_READ_CMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
buf[i] = readb(mtk_nor->base + MTK_NOR_RDATA_REG);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static int mtk_nor_write_single_byte(struct mtk_nor *mtk_nor,
|
||||
int addr, int length, u8 *data)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
mtk_nor_set_addr(mtk_nor, addr);
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
writeb(*data++, mtk_nor->base + MTK_NOR_WDATA_REG);
|
||||
ret = mtk_nor_execute_cmd(mtk_nor, MTK_NOR_PIO_WR_CMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_nor_write_buffer(struct mtk_nor *mtk_nor, int addr,
|
||||
const u8 *buf)
|
||||
{
|
||||
int i, bufidx, data;
|
||||
|
||||
mtk_nor_set_addr(mtk_nor, addr);
|
||||
|
||||
bufidx = 0;
|
||||
for (i = 0; i < SFLASH_WRBUF_SIZE; i += 4) {
|
||||
data = buf[bufidx + 3]<<24 | buf[bufidx + 2]<<16 |
|
||||
buf[bufidx + 1]<<8 | buf[bufidx];
|
||||
bufidx += 4;
|
||||
writel(data, mtk_nor->base + MTK_NOR_PP_DATA_REG);
|
||||
}
|
||||
return mtk_nor_execute_cmd(mtk_nor, MTK_NOR_WR_CMD);
|
||||
}
|
||||
|
||||
static ssize_t mtk_nor_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
const u_char *buf)
|
||||
{
|
||||
int ret;
|
||||
struct mtk_nor *mtk_nor = nor->priv;
|
||||
size_t i;
|
||||
|
||||
ret = mtk_nor_write_buffer_enable(mtk_nor);
|
||||
if (ret < 0) {
|
||||
dev_warn(mtk_nor->dev, "write buffer enable failed!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i + SFLASH_WRBUF_SIZE <= len; i += SFLASH_WRBUF_SIZE) {
|
||||
ret = mtk_nor_write_buffer(mtk_nor, to, buf);
|
||||
if (ret < 0) {
|
||||
dev_err(mtk_nor->dev, "write buffer failed!\n");
|
||||
return ret;
|
||||
}
|
||||
to += SFLASH_WRBUF_SIZE;
|
||||
buf += SFLASH_WRBUF_SIZE;
|
||||
}
|
||||
ret = mtk_nor_write_buffer_disable(mtk_nor);
|
||||
if (ret < 0) {
|
||||
dev_warn(mtk_nor->dev, "write buffer disable failed!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (i < len) {
|
||||
ret = mtk_nor_write_single_byte(mtk_nor, to,
|
||||
(int)(len - i), (u8 *)buf);
|
||||
if (ret < 0) {
|
||||
dev_err(mtk_nor->dev, "write single byte failed!\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int mtk_nor_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
struct mtk_nor *mtk_nor = nor->priv;
|
||||
|
||||
switch (opcode) {
|
||||
case SPINOR_OP_RDSR:
|
||||
ret = mtk_nor_execute_cmd(mtk_nor, MTK_NOR_RDSR_CMD);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (len == 1)
|
||||
*buf = readb(mtk_nor->base + MTK_NOR_RDSR_REG);
|
||||
else
|
||||
dev_err(mtk_nor->dev, "len should be 1 for read status!\n");
|
||||
break;
|
||||
default:
|
||||
ret = mtk_nor_do_tx_rx(mtk_nor, opcode, NULL, 0, buf, len);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
|
||||
size_t len)
|
||||
{
|
||||
int ret;
|
||||
struct mtk_nor *mtk_nor = nor->priv;
|
||||
|
||||
switch (opcode) {
|
||||
case SPINOR_OP_WRSR:
|
||||
/* We only handle 1 byte */
|
||||
ret = mtk_nor_wr_sr(mtk_nor, *buf);
|
||||
break;
|
||||
default:
|
||||
ret = mtk_nor_do_tx_rx(mtk_nor, opcode, buf, len, NULL, 0);
|
||||
if (ret)
|
||||
dev_warn(mtk_nor->dev, "write reg failure!\n");
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_nor_disable_clk(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
clk_disable_unprepare(mtk_nor->spi_clk);
|
||||
clk_disable_unprepare(mtk_nor->nor_clk);
|
||||
}
|
||||
|
||||
static int mtk_nor_enable_clk(struct mtk_nor *mtk_nor)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(mtk_nor->spi_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(mtk_nor->nor_clk);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(mtk_nor->spi_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_nor_controller_ops mtk_controller_ops = {
|
||||
.read_reg = mtk_nor_read_reg,
|
||||
.write_reg = mtk_nor_write_reg,
|
||||
.read = mtk_nor_read,
|
||||
.write = mtk_nor_write,
|
||||
};
|
||||
|
||||
static int mtk_nor_init(struct mtk_nor *mtk_nor,
|
||||
struct device_node *flash_node)
|
||||
{
|
||||
const struct spi_nor_hwcaps hwcaps = {
|
||||
.mask = SNOR_HWCAPS_READ |
|
||||
SNOR_HWCAPS_READ_FAST |
|
||||
SNOR_HWCAPS_READ_1_1_2 |
|
||||
SNOR_HWCAPS_PP,
|
||||
};
|
||||
int ret;
|
||||
struct spi_nor *nor;
|
||||
|
||||
/* initialize controller to accept commands */
|
||||
writel(MTK_NOR_ENABLE_SF_CMD, mtk_nor->base + MTK_NOR_WRPROT_REG);
|
||||
|
||||
nor = &mtk_nor->nor;
|
||||
nor->dev = mtk_nor->dev;
|
||||
nor->priv = mtk_nor;
|
||||
spi_nor_set_flash_node(nor, flash_node);
|
||||
nor->controller_ops = &mtk_controller_ops;
|
||||
|
||||
nor->mtd.name = "mtk_nor";
|
||||
/* initialized with NULL */
|
||||
ret = spi_nor_scan(nor, NULL, &hwcaps);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mtd_device_register(&nor->mtd, NULL, 0);
|
||||
}
|
||||
|
||||
static int mtk_nor_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *flash_np;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
struct mtk_nor *mtk_nor;
|
||||
|
||||
if (!pdev->dev.of_node) {
|
||||
dev_err(&pdev->dev, "No DT found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mtk_nor = devm_kzalloc(&pdev->dev, sizeof(*mtk_nor), GFP_KERNEL);
|
||||
if (!mtk_nor)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, mtk_nor);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
mtk_nor->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(mtk_nor->base))
|
||||
return PTR_ERR(mtk_nor->base);
|
||||
|
||||
mtk_nor->spi_clk = devm_clk_get(&pdev->dev, "spi");
|
||||
if (IS_ERR(mtk_nor->spi_clk))
|
||||
return PTR_ERR(mtk_nor->spi_clk);
|
||||
|
||||
mtk_nor->nor_clk = devm_clk_get(&pdev->dev, "sf");
|
||||
if (IS_ERR(mtk_nor->nor_clk))
|
||||
return PTR_ERR(mtk_nor->nor_clk);
|
||||
|
||||
mtk_nor->dev = &pdev->dev;
|
||||
|
||||
ret = mtk_nor_enable_clk(mtk_nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* only support one attached flash */
|
||||
flash_np = of_get_next_available_child(pdev->dev.of_node, NULL);
|
||||
if (!flash_np) {
|
||||
dev_err(&pdev->dev, "no SPI flash device to configure\n");
|
||||
ret = -ENODEV;
|
||||
goto nor_free;
|
||||
}
|
||||
ret = mtk_nor_init(mtk_nor, flash_np);
|
||||
|
||||
nor_free:
|
||||
if (ret)
|
||||
mtk_nor_disable_clk(mtk_nor);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_nor *mtk_nor = platform_get_drvdata(pdev);
|
||||
|
||||
mtk_nor_disable_clk(mtk_nor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mtk_nor_suspend(struct device *dev)
|
||||
{
|
||||
struct mtk_nor *mtk_nor = dev_get_drvdata(dev);
|
||||
|
||||
mtk_nor_disable_clk(mtk_nor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_nor_resume(struct device *dev)
|
||||
{
|
||||
struct mtk_nor *mtk_nor = dev_get_drvdata(dev);
|
||||
|
||||
return mtk_nor_enable_clk(mtk_nor);
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops mtk_nor_dev_pm_ops = {
|
||||
.suspend = mtk_nor_suspend,
|
||||
.resume = mtk_nor_resume,
|
||||
};
|
||||
|
||||
#define MTK_NOR_DEV_PM_OPS (&mtk_nor_dev_pm_ops)
|
||||
#else
|
||||
#define MTK_NOR_DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static const struct of_device_id mtk_nor_of_ids[] = {
|
||||
{ .compatible = "mediatek,mt8173-nor"},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_nor_of_ids);
|
||||
|
||||
static struct platform_driver mtk_nor_driver = {
|
||||
.probe = mtk_nor_drv_probe,
|
||||
.remove = mtk_nor_drv_remove,
|
||||
.driver = {
|
||||
.name = "mtk-nor",
|
||||
.pm = MTK_NOR_DEV_PM_OPS,
|
||||
.of_match_table = mtk_nor_of_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(mtk_nor_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MediaTek SPI NOR Flash Driver");
|
|
@ -107,6 +107,7 @@ config REGULATOR_AD5398
|
|||
|
||||
config REGULATOR_ANATOP
|
||||
tristate "Freescale i.MX on-chip ANATOP LDO regulators"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
depends on MFD_SYSCON
|
||||
help
|
||||
Say y here to support Freescale i.MX on-chip ANATOP LDOs
|
||||
|
@ -613,6 +614,16 @@ config REGULATOR_MCP16502
|
|||
through the regulator interface. In addition it enables
|
||||
suspend-to-ram/standby transition.
|
||||
|
||||
config REGULATOR_MP5416
|
||||
tristate "Monolithic MP5416 PMIC"
|
||||
depends on I2C && OF
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Say y here to support the MP5416 PMIC. This will enable supports
|
||||
the software controllable 4 buck and 4 LDO regulators.
|
||||
Say M here if you want to include support for the regulator as a
|
||||
module.
|
||||
|
||||
config REGULATOR_MP8859
|
||||
tristate "MPS MP8859 regulator driver"
|
||||
depends on I2C
|
||||
|
@ -624,6 +635,13 @@ config REGULATOR_MP8859
|
|||
Say M here if you want to include support for the regulator as a
|
||||
module. The module will be named "mp8859".
|
||||
|
||||
config REGULATOR_MP886X
|
||||
tristate "MPS MP8869 regulator driver"
|
||||
depends on I2C && (OF || COMPILE_TEST)
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This driver supports the MP8869 voltage regulator.
|
||||
|
||||
config REGULATOR_MPQ7920
|
||||
tristate "Monolithic MPQ7920 PMIC"
|
||||
depends on I2C && OF
|
||||
|
|
|
@ -78,7 +78,9 @@ obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
|
|||
obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
|
||||
obj-$(CONFIG_REGULATOR_MCP16502) += mcp16502.o
|
||||
obj-$(CONFIG_REGULATOR_MP5416) += mp5416.o
|
||||
obj-$(CONFIG_REGULATOR_MP8859) += mp8859.o
|
||||
obj-$(CONFIG_REGULATOR_MP886X) += mp886x.o
|
||||
obj-$(CONFIG_REGULATOR_MPQ7920) += mpq7920.o
|
||||
obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
|
||||
obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o
|
||||
|
|
|
@ -305,9 +305,13 @@ static int anatop_regulator_probe(struct platform_device *pdev)
|
|||
/* register regulator */
|
||||
rdev = devm_regulator_register(dev, rdesc, &config);
|
||||
if (IS_ERR(rdev)) {
|
||||
dev_err(dev, "failed to register %s\n",
|
||||
ret = PTR_ERR(rdev);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
dev_dbg(dev, "failed to register %s, deferring...\n",
|
||||
rdesc->name);
|
||||
return PTR_ERR(rdev);
|
||||
else
|
||||
dev_err(dev, "failed to register %s\n", rdesc->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, rdev);
|
||||
|
|
|
@ -381,8 +381,7 @@ static int axp20x_set_ramp_delay(struct regulator_dev *rdev, int ramp)
|
|||
mask = AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_RATE_MASK |
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN_MASK;
|
||||
enable = (ramp > 0) ?
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN :
|
||||
!AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN;
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_DCDC2_EN : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -393,8 +392,7 @@ static int axp20x_set_ramp_delay(struct regulator_dev *rdev, int ramp)
|
|||
mask = AXP20X_DCDC2_LDO3_V_RAMP_LDO3_RATE_MASK |
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN_MASK;
|
||||
enable = (ramp > 0) ?
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN :
|
||||
!AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN;
|
||||
AXP20X_DCDC2_LDO3_V_RAMP_LDO3_EN : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1849,7 +1849,6 @@ struct regulator *_regulator_get(struct device *dev, const char *id,
|
|||
{
|
||||
struct regulator_dev *rdev;
|
||||
struct regulator *regulator;
|
||||
const char *devname = dev ? dev_name(dev) : "deviceless";
|
||||
struct device_link *link;
|
||||
int ret;
|
||||
|
||||
|
@ -1887,9 +1886,7 @@ struct regulator *_regulator_get(struct device *dev, const char *id,
|
|||
* enabled, even if it isn't hooked up, and just
|
||||
* provide a dummy.
|
||||
*/
|
||||
dev_warn(dev,
|
||||
"%s supply %s not found, using dummy regulator\n",
|
||||
devname, id);
|
||||
dev_warn(dev, "supply %s not found, using dummy regulator\n", id);
|
||||
rdev = dummy_regulator_rdev;
|
||||
get_device(&rdev->dev);
|
||||
break;
|
||||
|
|
|
@ -73,7 +73,7 @@ struct da9062_regulators {
|
|||
int irq_ldo_lim;
|
||||
unsigned n_regulators;
|
||||
/* Array size to be defined during init. Keep at end. */
|
||||
struct da9062_regulator regulator[0];
|
||||
struct da9062_regulator regulator[];
|
||||
};
|
||||
|
||||
/* Regulator operations */
|
||||
|
|
|
@ -66,7 +66,7 @@ struct da9063_regulator_data {
|
|||
};
|
||||
|
||||
struct da9063_regulators_pdata {
|
||||
unsigned n_regulators;
|
||||
unsigned int n_regulators;
|
||||
struct da9063_regulator_data *regulator_data;
|
||||
};
|
||||
|
||||
|
@ -100,6 +100,7 @@ struct da9063_regulator_info {
|
|||
.desc.vsel_mask = DA9063_V##regl_name##_MASK, \
|
||||
.desc.linear_min_sel = DA9063_V##regl_name##_BIAS, \
|
||||
.sleep = BFIELD(DA9063_REG_V##regl_name##_A, DA9063_LDO_SL), \
|
||||
.suspend = BFIELD(DA9063_REG_##regl_name##_CONT, DA9063_LDO_CONF), \
|
||||
.suspend_sleep = BFIELD(DA9063_REG_V##regl_name##_B, DA9063_LDO_SL), \
|
||||
.suspend_vsel_reg = DA9063_REG_V##regl_name##_B
|
||||
|
||||
|
@ -124,6 +125,7 @@ struct da9063_regulator_info {
|
|||
.desc.vsel_mask = DA9063_VBUCK_MASK, \
|
||||
.desc.linear_min_sel = DA9063_VBUCK_BIAS, \
|
||||
.sleep = BFIELD(DA9063_REG_V##regl_name##_A, DA9063_BUCK_SL), \
|
||||
.suspend = BFIELD(DA9063_REG_##regl_name##_CONT, DA9063_BUCK_CONF), \
|
||||
.suspend_sleep = BFIELD(DA9063_REG_V##regl_name##_B, DA9063_BUCK_SL), \
|
||||
.suspend_vsel_reg = DA9063_REG_V##regl_name##_B, \
|
||||
.mode = BFIELD(DA9063_REG_##regl_name##_CFG, DA9063_BUCK_MODE_MASK)
|
||||
|
@ -131,7 +133,7 @@ struct da9063_regulator_info {
|
|||
/* Defines asignment of regulators info table to chip model */
|
||||
struct da9063_dev_model {
|
||||
const struct da9063_regulator_info *regulator_info;
|
||||
unsigned n_regulators;
|
||||
unsigned int n_regulators;
|
||||
enum da9063_type type;
|
||||
};
|
||||
|
||||
|
@ -150,9 +152,9 @@ struct da9063_regulator {
|
|||
|
||||
/* Encapsulates all information for the regulators driver */
|
||||
struct da9063_regulators {
|
||||
unsigned n_regulators;
|
||||
unsigned int n_regulators;
|
||||
/* Array size to be defined during init. Keep at end. */
|
||||
struct da9063_regulator regulator[0];
|
||||
struct da9063_regulator regulator[];
|
||||
};
|
||||
|
||||
/* BUCK modes for DA9063 */
|
||||
|
@ -165,38 +167,46 @@ enum {
|
|||
|
||||
/* Regulator operations */
|
||||
|
||||
/* Current limits array (in uA) for BCORE1, BCORE2, BPRO.
|
||||
Entry indexes corresponds to register values. */
|
||||
/*
|
||||
* Current limits array (in uA) for BCORE1, BCORE2, BPRO.
|
||||
* Entry indexes corresponds to register values.
|
||||
*/
|
||||
static const unsigned int da9063_buck_a_limits[] = {
|
||||
500000, 600000, 700000, 800000, 900000, 1000000, 1100000, 1200000,
|
||||
1300000, 1400000, 1500000, 1600000, 1700000, 1800000, 1900000, 2000000
|
||||
};
|
||||
|
||||
/* Current limits array (in uA) for BMEM, BIO, BPERI.
|
||||
Entry indexes corresponds to register values. */
|
||||
/*
|
||||
* Current limits array (in uA) for BMEM, BIO, BPERI.
|
||||
* Entry indexes corresponds to register values.
|
||||
*/
|
||||
static const unsigned int da9063_buck_b_limits[] = {
|
||||
1500000, 1600000, 1700000, 1800000, 1900000, 2000000, 2100000, 2200000,
|
||||
2300000, 2400000, 2500000, 2600000, 2700000, 2800000, 2900000, 3000000
|
||||
};
|
||||
|
||||
/* Current limits array (in uA) for merged BCORE1 and BCORE2.
|
||||
Entry indexes corresponds to register values. */
|
||||
/*
|
||||
* Current limits array (in uA) for merged BCORE1 and BCORE2.
|
||||
* Entry indexes corresponds to register values.
|
||||
*/
|
||||
static const unsigned int da9063_bcores_merged_limits[] = {
|
||||
1000000, 1200000, 1400000, 1600000, 1800000, 2000000, 2200000, 2400000,
|
||||
2600000, 2800000, 3000000, 3200000, 3400000, 3600000, 3800000, 4000000
|
||||
};
|
||||
|
||||
/* Current limits array (in uA) for merged BMEM and BIO.
|
||||
Entry indexes corresponds to register values. */
|
||||
/*
|
||||
* Current limits array (in uA) for merged BMEM and BIO.
|
||||
* Entry indexes corresponds to register values.
|
||||
*/
|
||||
static const unsigned int da9063_bmem_bio_merged_limits[] = {
|
||||
3000000, 3200000, 3400000, 3600000, 3800000, 4000000, 4200000, 4400000,
|
||||
4600000, 4800000, 5000000, 5200000, 5400000, 5600000, 5800000, 6000000
|
||||
};
|
||||
|
||||
static int da9063_buck_set_mode(struct regulator_dev *rdev, unsigned mode)
|
||||
static int da9063_buck_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
unsigned val;
|
||||
unsigned int val;
|
||||
|
||||
switch (mode) {
|
||||
case REGULATOR_MODE_FAST:
|
||||
|
@ -221,10 +231,9 @@ static int da9063_buck_set_mode(struct regulator_dev *rdev, unsigned mode)
|
|||
* There are 3 modes to map to: FAST, NORMAL, and STANDBY.
|
||||
*/
|
||||
|
||||
static unsigned da9063_buck_get_mode(struct regulator_dev *rdev)
|
||||
static unsigned int da9063_buck_get_mode(struct regulator_dev *rdev)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
struct regmap_field *field;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
|
@ -245,18 +254,7 @@ static unsigned da9063_buck_get_mode(struct regulator_dev *rdev)
|
|||
return REGULATOR_MODE_NORMAL;
|
||||
}
|
||||
|
||||
/* Detect current regulator state */
|
||||
ret = regmap_field_read(regl->suspend, &val);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
/* Read regulator mode from proper register, depending on state */
|
||||
if (val)
|
||||
field = regl->suspend_sleep;
|
||||
else
|
||||
field = regl->sleep;
|
||||
|
||||
ret = regmap_field_read(field, &val);
|
||||
ret = regmap_field_read(regl->sleep, &val);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
|
@ -271,10 +269,10 @@ static unsigned da9063_buck_get_mode(struct regulator_dev *rdev)
|
|||
* There are 2 modes to map to: NORMAL and STANDBY (sleep) for each state.
|
||||
*/
|
||||
|
||||
static int da9063_ldo_set_mode(struct regulator_dev *rdev, unsigned mode)
|
||||
static int da9063_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
unsigned val;
|
||||
unsigned int val;
|
||||
|
||||
switch (mode) {
|
||||
case REGULATOR_MODE_NORMAL:
|
||||
|
@ -290,24 +288,12 @@ static int da9063_ldo_set_mode(struct regulator_dev *rdev, unsigned mode)
|
|||
return regmap_field_write(regl->sleep, val);
|
||||
}
|
||||
|
||||
static unsigned da9063_ldo_get_mode(struct regulator_dev *rdev)
|
||||
static unsigned int da9063_ldo_get_mode(struct regulator_dev *rdev)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
struct regmap_field *field;
|
||||
int ret, val;
|
||||
|
||||
/* Detect current regulator state */
|
||||
ret = regmap_field_read(regl->suspend, &val);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
/* Read regulator mode from proper register, depending on state */
|
||||
if (val)
|
||||
field = regl->suspend_sleep;
|
||||
else
|
||||
field = regl->sleep;
|
||||
|
||||
ret = regmap_field_read(field, &val);
|
||||
ret = regmap_field_read(regl->sleep, &val);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
|
@ -383,7 +369,8 @@ static int da9063_suspend_disable(struct regulator_dev *rdev)
|
|||
return regmap_field_write(regl->suspend, 0);
|
||||
}
|
||||
|
||||
static int da9063_buck_set_suspend_mode(struct regulator_dev *rdev, unsigned mode)
|
||||
static int da9063_buck_set_suspend_mode(struct regulator_dev *rdev,
|
||||
unsigned int mode)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
int val;
|
||||
|
@ -405,10 +392,11 @@ static int da9063_buck_set_suspend_mode(struct regulator_dev *rdev, unsigned mod
|
|||
return regmap_field_write(regl->mode, val);
|
||||
}
|
||||
|
||||
static int da9063_ldo_set_suspend_mode(struct regulator_dev *rdev, unsigned mode)
|
||||
static int da9063_ldo_set_suspend_mode(struct regulator_dev *rdev,
|
||||
unsigned int mode)
|
||||
{
|
||||
struct da9063_regulator *regl = rdev_get_drvdata(rdev);
|
||||
unsigned val;
|
||||
unsigned int val;
|
||||
|
||||
switch (mode) {
|
||||
case REGULATOR_MODE_NORMAL:
|
||||
|
@ -465,42 +453,36 @@ static const struct da9063_regulator_info da9063_regulator_info[] = {
|
|||
da9063_buck_a_limits,
|
||||
DA9063_REG_BUCK_ILIM_C, DA9063_BCORE1_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BCORE1),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE1_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BCORE2, 300, 10, 1570,
|
||||
da9063_buck_a_limits,
|
||||
DA9063_REG_BUCK_ILIM_C, DA9063_BCORE2_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BCORE2),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE2_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BPRO, 530, 10, 1800,
|
||||
da9063_buck_a_limits,
|
||||
DA9063_REG_BUCK_ILIM_B, DA9063_BPRO_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BPRO),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBPRO_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BMEM, 800, 20, 3340,
|
||||
da9063_buck_b_limits,
|
||||
DA9063_REG_BUCK_ILIM_A, DA9063_BMEM_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BMEM),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBMEM_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BIO, 800, 20, 3340,
|
||||
da9063_buck_b_limits,
|
||||
DA9063_REG_BUCK_ILIM_A, DA9063_BIO_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BIO),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_2, DA9063_VBIO_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BPERI, 800, 20, 3340,
|
||||
da9063_buck_b_limits,
|
||||
DA9063_REG_BUCK_ILIM_B, DA9063_BPERI_ILIM_MASK),
|
||||
DA9063_BUCK_COMMON_FIELDS(BPERI),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBPERI_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BCORES_MERGED, 300, 10, 1570,
|
||||
|
@ -508,7 +490,6 @@ static const struct da9063_regulator_info da9063_regulator_info[] = {
|
|||
DA9063_REG_BUCK_ILIM_C, DA9063_BCORE1_ILIM_MASK),
|
||||
/* BCORES_MERGED uses the same register fields as BCORE1 */
|
||||
DA9063_BUCK_COMMON_FIELDS(BCORE1),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBCORE1_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_BUCK(DA9063, BMEM_BIO_MERGED, 800, 20, 3340,
|
||||
|
@ -516,21 +497,17 @@ static const struct da9063_regulator_info da9063_regulator_info[] = {
|
|||
DA9063_REG_BUCK_ILIM_A, DA9063_BMEM_ILIM_MASK),
|
||||
/* BMEM_BIO_MERGED uses the same register fields as BMEM */
|
||||
DA9063_BUCK_COMMON_FIELDS(BMEM),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VBMEM_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO3, 900, 20, 3440),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO3_SEL),
|
||||
.oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO3_LIM),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO7, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO7_CONT, DA9063_VLDO7_SEL),
|
||||
.oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO7_LIM),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO8, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO8_CONT, DA9063_VLDO8_SEL),
|
||||
.oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO8_LIM),
|
||||
},
|
||||
{
|
||||
|
@ -539,36 +516,29 @@ static const struct da9063_regulator_info da9063_regulator_info[] = {
|
|||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO11, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO11_CONT, DA9063_VLDO11_SEL),
|
||||
.oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO11_LIM),
|
||||
},
|
||||
|
||||
/* The following LDOs are present only on DA9063, not on DA9063L */
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO1, 600, 20, 1860),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO1_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO2, 600, 20, 1860),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_1, DA9063_VLDO2_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO4, 900, 20, 3440),
|
||||
.suspend = BFIELD(DA9063_REG_DVC_2, DA9063_VLDO4_SEL),
|
||||
.oc_event = BFIELD(DA9063_REG_STATUS_D, DA9063_LDO4_LIM),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO5, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO5_CONT, DA9063_VLDO5_SEL),
|
||||
},
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO6, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO6_CONT, DA9063_VLDO6_SEL),
|
||||
},
|
||||
|
||||
{
|
||||
DA9063_LDO(DA9063, LDO10, 900, 50, 3600),
|
||||
.suspend = BFIELD(DA9063_REG_LDO10_CONT, DA9063_VLDO10_SEL),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -593,7 +563,7 @@ static irqreturn_t da9063_ldo_lim_event(int irq, void *data)
|
|||
struct da9063_regulators *regulators = data;
|
||||
struct da9063 *hw = regulators->regulator[0].hw;
|
||||
struct da9063_regulator *regl;
|
||||
int bits, i , ret;
|
||||
int bits, i, ret;
|
||||
|
||||
ret = regmap_read(hw->regmap, DA9063_REG_STATUS_D, &bits);
|
||||
if (ret < 0)
|
||||
|
@ -867,12 +837,10 @@ static int da9063_regulator_probe(struct platform_device *pdev)
|
|||
NULL, da9063_ldo_lim_event,
|
||||
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||
"LDO_LIM", regulators);
|
||||
if (ret) {
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to request LDO_LIM IRQ.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver da9063_regulator_driver = {
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
//
|
||||
// mp5416.c - regulator driver for mps mp5416
|
||||
//
|
||||
// Copyright 2020 Monolithic Power Systems, Inc
|
||||
//
|
||||
// Author: Saravanan Sekar <sravanhome@gmail.com>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#define MP5416_REG_CTL0 0x00
|
||||
#define MP5416_REG_CTL1 0x01
|
||||
#define MP5416_REG_CTL2 0x02
|
||||
#define MP5416_REG_ILIM 0x03
|
||||
#define MP5416_REG_BUCK1 0x04
|
||||
#define MP5416_REG_BUCK2 0x05
|
||||
#define MP5416_REG_BUCK3 0x06
|
||||
#define MP5416_REG_BUCK4 0x07
|
||||
#define MP5416_REG_LDO1 0x08
|
||||
#define MP5416_REG_LDO2 0x09
|
||||
#define MP5416_REG_LDO3 0x0a
|
||||
#define MP5416_REG_LDO4 0x0b
|
||||
|
||||
#define MP5416_REGULATOR_EN BIT(7)
|
||||
#define MP5416_MASK_VSET 0x7f
|
||||
#define MP5416_MASK_BUCK1_ILIM 0xc0
|
||||
#define MP5416_MASK_BUCK2_ILIM 0x0c
|
||||
#define MP5416_MASK_BUCK3_ILIM 0x30
|
||||
#define MP5416_MASK_BUCK4_ILIM 0x03
|
||||
#define MP5416_MASK_DVS_SLEWRATE 0xc0
|
||||
|
||||
/* values in uV */
|
||||
#define MP5416_VOLT1_MIN 600000
|
||||
#define MP5416_VOLT1_MAX 2187500
|
||||
#define MP5416_VOLT1_STEP 12500
|
||||
#define MP5416_VOLT2_MIN 800000
|
||||
#define MP5416_VOLT2_MAX 3975000
|
||||
#define MP5416_VOLT2_STEP 25000
|
||||
|
||||
#define MP5416_VOLT1_RANGE \
|
||||
((MP5416_VOLT1_MAX - MP5416_VOLT1_MIN)/MP5416_VOLT1_STEP + 1)
|
||||
#define MP5416_VOLT2_RANGE \
|
||||
((MP5416_VOLT2_MAX - MP5416_VOLT2_MIN)/MP5416_VOLT2_STEP + 1)
|
||||
|
||||
#define MP5416BUCK(_name, _id, _ilim, _dreg, _dval, _vsel) \
|
||||
[MP5416_BUCK ## _id] = { \
|
||||
.id = MP5416_BUCK ## _id, \
|
||||
.name = _name, \
|
||||
.of_match = _name, \
|
||||
.regulators_node = "regulators", \
|
||||
.ops = &mp5416_buck_ops, \
|
||||
.min_uV = MP5416_VOLT ##_vsel## _MIN, \
|
||||
.uV_step = MP5416_VOLT ##_vsel## _STEP, \
|
||||
.n_voltages = MP5416_VOLT ##_vsel## _RANGE, \
|
||||
.curr_table = _ilim, \
|
||||
.n_current_limits = ARRAY_SIZE(_ilim), \
|
||||
.csel_reg = MP5416_REG_ILIM, \
|
||||
.csel_mask = MP5416_MASK_BUCK ## _id ##_ILIM, \
|
||||
.vsel_reg = MP5416_REG_BUCK ## _id, \
|
||||
.vsel_mask = MP5416_MASK_VSET, \
|
||||
.enable_reg = MP5416_REG_BUCK ## _id, \
|
||||
.enable_mask = MP5416_REGULATOR_EN, \
|
||||
.active_discharge_on = _dval, \
|
||||
.active_discharge_reg = _dreg, \
|
||||
.active_discharge_mask = _dval, \
|
||||
.owner = THIS_MODULE, \
|
||||
}
|
||||
|
||||
#define MP5416LDO(_name, _id, _dval) \
|
||||
[MP5416_LDO ## _id] = { \
|
||||
.id = MP5416_LDO ## _id, \
|
||||
.name = _name, \
|
||||
.of_match = _name, \
|
||||
.regulators_node = "regulators", \
|
||||
.ops = &mp5416_ldo_ops, \
|
||||
.min_uV = MP5416_VOLT2_MIN, \
|
||||
.uV_step = MP5416_VOLT2_STEP, \
|
||||
.n_voltages = MP5416_VOLT2_RANGE, \
|
||||
.vsel_reg = MP5416_REG_LDO ##_id, \
|
||||
.vsel_mask = MP5416_MASK_VSET, \
|
||||
.enable_reg = MP5416_REG_LDO ##_id, \
|
||||
.enable_mask = MP5416_REGULATOR_EN, \
|
||||
.active_discharge_on = _dval, \
|
||||
.active_discharge_reg = MP5416_REG_CTL2, \
|
||||
.active_discharge_mask = _dval, \
|
||||
.owner = THIS_MODULE, \
|
||||
}
|
||||
|
||||
enum mp5416_regulators {
|
||||
MP5416_BUCK1,
|
||||
MP5416_BUCK2,
|
||||
MP5416_BUCK3,
|
||||
MP5416_BUCK4,
|
||||
MP5416_LDO1,
|
||||
MP5416_LDO2,
|
||||
MP5416_LDO3,
|
||||
MP5416_LDO4,
|
||||
MP5416_MAX_REGULATORS,
|
||||
};
|
||||
|
||||
static const struct regmap_config mp5416_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x0d,
|
||||
};
|
||||
|
||||
/* Current limits array (in uA)
|
||||
* ILIM1 & ILIM3
|
||||
*/
|
||||
static const unsigned int mp5416_I_limits1[] = {
|
||||
3800000, 4600000, 5600000, 6800000
|
||||
};
|
||||
|
||||
/* ILIM2 & ILIM4 */
|
||||
static const unsigned int mp5416_I_limits2[] = {
|
||||
2200000, 3200000, 4200000, 5200000
|
||||
};
|
||||
|
||||
static int mp5416_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay);
|
||||
|
||||
static const struct regulator_ops mp5416_ldo_ops = {
|
||||
.enable = regulator_enable_regmap,
|
||||
.disable = regulator_disable_regmap,
|
||||
.is_enabled = regulator_is_enabled_regmap,
|
||||
.list_voltage = regulator_list_voltage_linear,
|
||||
.map_voltage = regulator_map_voltage_linear,
|
||||
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
||||
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
||||
.set_active_discharge = regulator_set_active_discharge_regmap,
|
||||
};
|
||||
|
||||
static const struct regulator_ops mp5416_buck_ops = {
|
||||
.enable = regulator_enable_regmap,
|
||||
.disable = regulator_disable_regmap,
|
||||
.is_enabled = regulator_is_enabled_regmap,
|
||||
.list_voltage = regulator_list_voltage_linear,
|
||||
.map_voltage = regulator_map_voltage_linear,
|
||||
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
||||
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
||||
.set_active_discharge = regulator_set_active_discharge_regmap,
|
||||
.get_current_limit = regulator_get_current_limit_regmap,
|
||||
.set_current_limit = regulator_set_current_limit_regmap,
|
||||
.set_ramp_delay = mp5416_set_ramp_delay,
|
||||
};
|
||||
|
||||
static struct regulator_desc mp5416_regulators_desc[MP5416_MAX_REGULATORS] = {
|
||||
MP5416BUCK("buck1", 1, mp5416_I_limits1, MP5416_REG_CTL1, BIT(0), 1),
|
||||
MP5416BUCK("buck2", 2, mp5416_I_limits2, MP5416_REG_CTL1, BIT(1), 2),
|
||||
MP5416BUCK("buck3", 3, mp5416_I_limits1, MP5416_REG_CTL1, BIT(2), 1),
|
||||
MP5416BUCK("buck4", 4, mp5416_I_limits2, MP5416_REG_CTL2, BIT(5), 2),
|
||||
MP5416LDO("ldo1", 1, BIT(4)),
|
||||
MP5416LDO("ldo2", 2, BIT(3)),
|
||||
MP5416LDO("ldo3", 3, BIT(2)),
|
||||
MP5416LDO("ldo4", 4, BIT(1)),
|
||||
};
|
||||
|
||||
/*
|
||||
* DVS ramp rate BUCK1 to BUCK4
|
||||
* 00: 32mV/us
|
||||
* 01: 16mV/us
|
||||
* 10: 8mV/us
|
||||
* 11: 4mV/us
|
||||
*/
|
||||
static int mp5416_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay)
|
||||
{
|
||||
unsigned int ramp_val;
|
||||
|
||||
if (ramp_delay > 32000 || ramp_delay < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (ramp_delay <= 4000)
|
||||
ramp_val = 3;
|
||||
else if (ramp_delay <= 8000)
|
||||
ramp_val = 2;
|
||||
else if (ramp_delay <= 16000)
|
||||
ramp_val = 1;
|
||||
else
|
||||
ramp_val = 0;
|
||||
|
||||
return regmap_update_bits(rdev->regmap, MP5416_REG_CTL2,
|
||||
MP5416_MASK_DVS_SLEWRATE, ramp_val << 6);
|
||||
}
|
||||
|
||||
static int mp5416_i2c_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct regulator_config config = { NULL, };
|
||||
struct regulator_dev *rdev;
|
||||
struct regmap *regmap;
|
||||
int i;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &mp5416_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(dev, "Failed to allocate regmap!\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
config.dev = dev;
|
||||
config.regmap = regmap;
|
||||
|
||||
for (i = 0; i < MP5416_MAX_REGULATORS; i++) {
|
||||
rdev = devm_regulator_register(dev,
|
||||
&mp5416_regulators_desc[i],
|
||||
&config);
|
||||
if (IS_ERR(rdev)) {
|
||||
dev_err(dev, "Failed to register regulator!\n");
|
||||
return PTR_ERR(rdev);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mp5416_of_match[] = {
|
||||
{ .compatible = "mps,mp5416" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mp5416_of_match);
|
||||
|
||||
static const struct i2c_device_id mp5416_id[] = {
|
||||
{ "mp5416", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp5416_id);
|
||||
|
||||
static struct i2c_driver mp5416_regulator_driver = {
|
||||
.driver = {
|
||||
.name = "mp5416",
|
||||
.of_match_table = of_match_ptr(mp5416_of_match),
|
||||
},
|
||||
.probe_new = mp5416_i2c_probe,
|
||||
.id_table = mp5416_id,
|
||||
};
|
||||
module_i2c_driver(mp5416_regulator_driver);
|
||||
|
||||
MODULE_AUTHOR("Saravanan Sekar <sravanhome@gmail.com>");
|
||||
MODULE_DESCRIPTION("MP5416 PMIC regulator driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -95,6 +95,7 @@ static const struct regulator_desc mp8859_regulators[] = {
|
|||
.id = 0,
|
||||
.type = REGULATOR_VOLTAGE,
|
||||
.name = "mp8859_dcdc",
|
||||
.supply_name = "vin",
|
||||
.of_match = of_match_ptr("mp8859_dcdc"),
|
||||
.n_voltages = VOL_MAX_IDX + 1,
|
||||
.linear_ranges = mp8859_dcdc_ranges,
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// MP8867/MP8869 regulator driver
|
||||
//
|
||||
// Copyright (C) 2020 Synaptics Incorporated
|
||||
//
|
||||
// Author: Jisheng Zhang <jszhang@kernel.org>
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
|
||||
#define MP886X_VSEL 0x00
|
||||
#define MP886X_V_BOOT (1 << 7)
|
||||
#define MP886X_SYSCNTLREG1 0x01
|
||||
#define MP886X_MODE (1 << 0)
|
||||
#define MP886X_GO (1 << 6)
|
||||
#define MP886X_EN (1 << 7)
|
||||
|
||||
struct mp886x_device_info {
|
||||
struct device *dev;
|
||||
struct regulator_desc desc;
|
||||
struct regulator_init_data *regulator;
|
||||
struct gpio_desc *en_gpio;
|
||||
u32 r[2];
|
||||
unsigned int sel;
|
||||
};
|
||||
|
||||
static int mp886x_set_mode(struct regulator_dev *rdev, unsigned int mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case REGULATOR_MODE_FAST:
|
||||
regmap_update_bits(rdev->regmap, MP886X_SYSCNTLREG1,
|
||||
MP886X_MODE, MP886X_MODE);
|
||||
break;
|
||||
case REGULATOR_MODE_NORMAL:
|
||||
regmap_update_bits(rdev->regmap, MP886X_SYSCNTLREG1,
|
||||
MP886X_MODE, 0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int mp886x_get_mode(struct regulator_dev *rdev)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(rdev->regmap, MP886X_SYSCNTLREG1, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (val & MP886X_MODE)
|
||||
return REGULATOR_MODE_FAST;
|
||||
else
|
||||
return REGULATOR_MODE_NORMAL;
|
||||
}
|
||||
|
||||
static int mp8869_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(rdev->regmap, MP886X_SYSCNTLREG1,
|
||||
MP886X_GO, MP886X_GO);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
sel <<= ffs(rdev->desc->vsel_mask) - 1;
|
||||
return regmap_update_bits(rdev->regmap, rdev->desc->vsel_reg,
|
||||
MP886X_V_BOOT | rdev->desc->vsel_mask, sel);
|
||||
}
|
||||
|
||||
static inline unsigned int mp8869_scale(unsigned int uv, u32 r1, u32 r2)
|
||||
{
|
||||
u32 tmp = uv * r1 / r2;
|
||||
|
||||
return uv + tmp;
|
||||
}
|
||||
|
||||
static int mp8869_get_voltage_sel(struct regulator_dev *rdev)
|
||||
{
|
||||
struct mp886x_device_info *di = rdev_get_drvdata(rdev);
|
||||
int ret, uv;
|
||||
unsigned int val;
|
||||
bool fbloop;
|
||||
|
||||
ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fbloop = val & MP886X_V_BOOT;
|
||||
if (fbloop) {
|
||||
uv = rdev->desc->min_uV;
|
||||
uv = mp8869_scale(uv, di->r[0], di->r[1]);
|
||||
return regulator_map_voltage_linear(rdev, uv, uv);
|
||||
}
|
||||
|
||||
val &= rdev->desc->vsel_mask;
|
||||
val >>= ffs(rdev->desc->vsel_mask) - 1;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static const struct regulator_ops mp8869_regulator_ops = {
|
||||
.set_voltage_sel = mp8869_set_voltage_sel,
|
||||
.get_voltage_sel = mp8869_get_voltage_sel,
|
||||
.set_voltage_time_sel = regulator_set_voltage_time_sel,
|
||||
.map_voltage = regulator_map_voltage_linear,
|
||||
.list_voltage = regulator_list_voltage_linear,
|
||||
.enable = regulator_enable_regmap,
|
||||
.disable = regulator_disable_regmap,
|
||||
.is_enabled = regulator_is_enabled_regmap,
|
||||
.set_mode = mp886x_set_mode,
|
||||
.get_mode = mp886x_get_mode,
|
||||
};
|
||||
|
||||
static int mp8867_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel)
|
||||
{
|
||||
struct mp886x_device_info *di = rdev_get_drvdata(rdev);
|
||||
int ret, delta;
|
||||
|
||||
ret = mp8869_set_voltage_sel(rdev, sel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
delta = di->sel - sel;
|
||||
if (abs(delta) <= 5)
|
||||
ret = regmap_update_bits(rdev->regmap, MP886X_SYSCNTLREG1,
|
||||
MP886X_GO, 0);
|
||||
di->sel = sel;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mp8867_get_voltage_sel(struct regulator_dev *rdev)
|
||||
{
|
||||
struct mp886x_device_info *di = rdev_get_drvdata(rdev);
|
||||
int ret, uv;
|
||||
unsigned int val;
|
||||
bool fbloop;
|
||||
|
||||
ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
fbloop = val & MP886X_V_BOOT;
|
||||
|
||||
val &= rdev->desc->vsel_mask;
|
||||
val >>= ffs(rdev->desc->vsel_mask) - 1;
|
||||
|
||||
if (fbloop) {
|
||||
uv = regulator_list_voltage_linear(rdev, val);
|
||||
uv = mp8869_scale(uv, di->r[0], di->r[1]);
|
||||
return regulator_map_voltage_linear(rdev, uv, uv);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static const struct regulator_ops mp8867_regulator_ops = {
|
||||
.set_voltage_sel = mp8867_set_voltage_sel,
|
||||
.get_voltage_sel = mp8867_get_voltage_sel,
|
||||
.set_voltage_time_sel = regulator_set_voltage_time_sel,
|
||||
.map_voltage = regulator_map_voltage_linear,
|
||||
.list_voltage = regulator_list_voltage_linear,
|
||||
.enable = regulator_enable_regmap,
|
||||
.disable = regulator_disable_regmap,
|
||||
.is_enabled = regulator_is_enabled_regmap,
|
||||
.set_mode = mp886x_set_mode,
|
||||
.get_mode = mp886x_get_mode,
|
||||
};
|
||||
|
||||
static int mp886x_regulator_register(struct mp886x_device_info *di,
|
||||
struct regulator_config *config)
|
||||
{
|
||||
struct regulator_desc *rdesc = &di->desc;
|
||||
struct regulator_dev *rdev;
|
||||
|
||||
rdesc->name = "mp886x-reg";
|
||||
rdesc->supply_name = "vin";
|
||||
rdesc->ops = of_device_get_match_data(di->dev);
|
||||
rdesc->type = REGULATOR_VOLTAGE;
|
||||
rdesc->n_voltages = 128;
|
||||
rdesc->enable_reg = MP886X_SYSCNTLREG1;
|
||||
rdesc->enable_mask = MP886X_EN;
|
||||
rdesc->min_uV = 600000;
|
||||
rdesc->uV_step = 10000;
|
||||
rdesc->vsel_reg = MP886X_VSEL;
|
||||
rdesc->vsel_mask = 0x3f;
|
||||
rdesc->owner = THIS_MODULE;
|
||||
|
||||
rdev = devm_regulator_register(di->dev, &di->desc, config);
|
||||
if (IS_ERR(rdev))
|
||||
return PTR_ERR(rdev);
|
||||
di->sel = rdesc->ops->get_voltage_sel(rdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct regmap_config mp886x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int mp886x_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct mp886x_device_info *di;
|
||||
struct regulator_config config = { };
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
di = devm_kzalloc(dev, sizeof(struct mp886x_device_info), GFP_KERNEL);
|
||||
if (!di)
|
||||
return -ENOMEM;
|
||||
|
||||
di->regulator = of_get_regulator_init_data(dev, np, &di->desc);
|
||||
if (!di->regulator) {
|
||||
dev_err(dev, "Platform data not found!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32_array(np, "mps,fb-voltage-divider",
|
||||
di->r, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
di->en_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(di->en_gpio))
|
||||
return PTR_ERR(di->en_gpio);
|
||||
|
||||
di->dev = dev;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &mp886x_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(dev, "Failed to allocate regmap!\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
i2c_set_clientdata(client, di);
|
||||
|
||||
config.dev = di->dev;
|
||||
config.init_data = di->regulator;
|
||||
config.regmap = regmap;
|
||||
config.driver_data = di;
|
||||
config.of_node = np;
|
||||
|
||||
ret = mp886x_regulator_register(di, &config);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "Failed to register regulator!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id mp886x_dt_ids[] = {
|
||||
{
|
||||
.compatible = "mps,mp8867",
|
||||
.data = &mp8867_regulator_ops
|
||||
},
|
||||
{
|
||||
.compatible = "mps,mp8869",
|
||||
.data = &mp8869_regulator_ops
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mp886x_dt_ids);
|
||||
|
||||
static const struct i2c_device_id mp886x_id[] = {
|
||||
{ "mp886x", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, mp886x_id);
|
||||
|
||||
static struct i2c_driver mp886x_regulator_driver = {
|
||||
.driver = {
|
||||
.name = "mp886x-regulator",
|
||||
.of_match_table = of_match_ptr(mp886x_dt_ids),
|
||||
},
|
||||
.probe = mp886x_i2c_probe,
|
||||
.id_table = mp886x_id,
|
||||
};
|
||||
module_i2c_driver(mp886x_regulator_driver);
|
||||
|
||||
MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
|
||||
MODULE_DESCRIPTION("MP886x regulator driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -354,6 +354,10 @@ static int pwm_regulator_probe(struct platform_device *pdev)
|
|||
drvdata->pwm = devm_pwm_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(drvdata->pwm)) {
|
||||
ret = PTR_ERR(drvdata->pwm);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
dev_dbg(&pdev->dev,
|
||||
"Failed to get PWM, deferring probe\n");
|
||||
else
|
||||
dev_err(&pdev->dev, "Failed to get PWM: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -925,12 +925,21 @@ static const struct rpm_regulator_data rpm_pm8921_regulators[] = {
|
|||
{ }
|
||||
};
|
||||
|
||||
static const struct rpm_regulator_data rpm_smb208_regulators[] = {
|
||||
{ "s1a", QCOM_RPM_SMB208_S1a, &smb208_smps, "vin_s1a" },
|
||||
{ "s1b", QCOM_RPM_SMB208_S1b, &smb208_smps, "vin_s1b" },
|
||||
{ "s2a", QCOM_RPM_SMB208_S2a, &smb208_smps, "vin_s2a" },
|
||||
{ "s2b", QCOM_RPM_SMB208_S2b, &smb208_smps, "vin_s2b" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct of_device_id rpm_of_match[] = {
|
||||
{ .compatible = "qcom,rpm-pm8018-regulators",
|
||||
.data = &rpm_pm8018_regulators },
|
||||
{ .compatible = "qcom,rpm-pm8058-regulators", .data = &rpm_pm8058_regulators },
|
||||
{ .compatible = "qcom,rpm-pm8901-regulators", .data = &rpm_pm8901_regulators },
|
||||
{ .compatible = "qcom,rpm-pm8921-regulators", .data = &rpm_pm8921_regulators },
|
||||
{ .compatible = "qcom,rpm-smb208-regulators", .data = &rpm_smb208_regulators },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rpm_of_match);
|
||||
|
|
|
@ -445,6 +445,44 @@ static const struct regulator_desc pm8994_lnldo = {
|
|||
.ops = &rpm_smps_ldo_ops_fixed,
|
||||
};
|
||||
|
||||
static const struct regulator_desc pmi8994_ftsmps = {
|
||||
.linear_ranges = (struct regulator_linear_range[]) {
|
||||
REGULATOR_LINEAR_RANGE(350000, 0, 199, 5000),
|
||||
REGULATOR_LINEAR_RANGE(700000, 200, 349, 10000),
|
||||
},
|
||||
.n_linear_ranges = 2,
|
||||
.n_voltages = 350,
|
||||
.ops = &rpm_smps_ldo_ops,
|
||||
};
|
||||
|
||||
static const struct regulator_desc pmi8994_hfsmps = {
|
||||
.linear_ranges = (struct regulator_linear_range[]) {
|
||||
REGULATOR_LINEAR_RANGE(350000, 0, 80, 12500),
|
||||
REGULATOR_LINEAR_RANGE(700000, 81, 141, 25000),
|
||||
},
|
||||
.n_linear_ranges = 2,
|
||||
.n_voltages = 142,
|
||||
.ops = &rpm_smps_ldo_ops,
|
||||
};
|
||||
|
||||
static const struct regulator_desc pmi8994_bby = {
|
||||
.linear_ranges = (struct regulator_linear_range[]) {
|
||||
REGULATOR_LINEAR_RANGE(3000000, 0, 44, 50000),
|
||||
},
|
||||
.n_linear_ranges = 1,
|
||||
.n_voltages = 45,
|
||||
.ops = &rpm_bob_ops,
|
||||
};
|
||||
|
||||
static const struct regulator_desc pmi8994_boost = {
|
||||
.linear_ranges = (struct regulator_linear_range[]) {
|
||||
REGULATOR_LINEAR_RANGE(4000000, 0, 30, 50000),
|
||||
},
|
||||
.n_linear_ranges = 1,
|
||||
.n_voltages = 31,
|
||||
.ops = &rpm_smps_ldo_ops,
|
||||
};
|
||||
|
||||
static const struct regulator_desc pm8998_ftsmps = {
|
||||
.linear_ranges = (struct regulator_linear_range[]) {
|
||||
REGULATOR_LINEAR_RANGE(320000, 0, 258, 4000),
|
||||
|
@ -780,6 +818,14 @@ static const struct rpm_regulator_data rpm_pm8994_regulators[] = {
|
|||
{}
|
||||
};
|
||||
|
||||
static const struct rpm_regulator_data rpm_pmi8994_regulators[] = {
|
||||
{ "s1", QCOM_SMD_RPM_SMPB, 1, &pmi8994_ftsmps, "vdd_s1" },
|
||||
{ "s2", QCOM_SMD_RPM_SMPB, 2, &pmi8994_hfsmps, "vdd_s2" },
|
||||
{ "s2", QCOM_SMD_RPM_SMPB, 3, &pmi8994_hfsmps, "vdd_s3" },
|
||||
{ "boost-bypass", QCOM_SMD_RPM_BBYB, 1, &pmi8994_bby, "vdd_bst_byp" },
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct rpm_regulator_data rpm_pm8998_regulators[] = {
|
||||
{ "s1", QCOM_SMD_RPM_SMPA, 1, &pm8998_ftsmps, "vdd_s1" },
|
||||
{ "s2", QCOM_SMD_RPM_SMPA, 2, &pm8998_ftsmps, "vdd_s2" },
|
||||
|
@ -862,6 +908,7 @@ static const struct of_device_id rpm_of_match[] = {
|
|||
{ .compatible = "qcom,rpm-pm8994-regulators", .data = &rpm_pm8994_regulators },
|
||||
{ .compatible = "qcom,rpm-pm8998-regulators", .data = &rpm_pm8998_regulators },
|
||||
{ .compatible = "qcom,rpm-pma8084-regulators", .data = &rpm_pma8084_regulators },
|
||||
{ .compatible = "qcom,rpm-pmi8994-regulators", .data = &rpm_pmi8994_regulators },
|
||||
{ .compatible = "qcom,rpm-pmi8998-regulators", .data = &rpm_pmi8998_regulators },
|
||||
{ .compatible = "qcom,rpm-pms405-regulators", .data = &rpm_pms405_regulators },
|
||||
{}
|
||||
|
|
|
@ -62,6 +62,13 @@ config SPI_ALTERA
|
|||
help
|
||||
This is the driver for the Altera SPI Controller.
|
||||
|
||||
config SPI_AR934X
|
||||
tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
|
||||
depends on ATH79 || COMPILE_TEST
|
||||
help
|
||||
This enables support for the SPI controller present on the
|
||||
Qualcomm Atheros AR934X/QCA95XX SoCs.
|
||||
|
||||
config SPI_ATH79
|
||||
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
|
||||
depends on ATH79 || COMPILE_TEST
|
||||
|
@ -264,6 +271,13 @@ config SPI_FALCON
|
|||
has only been tested with m25p80 type chips. The hardware has no
|
||||
support for other types of SPI peripherals.
|
||||
|
||||
config SPI_FSI
|
||||
tristate "FSI SPI driver"
|
||||
depends on FSI
|
||||
help
|
||||
This enables support for the driver for FSI bus attached SPI
|
||||
controllers.
|
||||
|
||||
config SPI_FSL_LPSPI
|
||||
tristate "Freescale i.MX LPSPI controller"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
|
@ -285,7 +299,6 @@ config SPI_HISI_SFC_V3XX
|
|||
tristate "HiSilicon SPI-NOR Flash Controller for Hi16XX chipsets"
|
||||
depends on (ARM64 && ACPI) || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
select CONFIG_MTD_SPI_NOR
|
||||
help
|
||||
This enables support for HiSilicon v3xx SPI-NOR flash controller
|
||||
found in hi16xx chipsets.
|
||||
|
@ -415,6 +428,7 @@ config SPI_FSL_ESPI
|
|||
|
||||
config SPI_MESON_SPICC
|
||||
tristate "Amlogic Meson SPICC controller"
|
||||
depends on COMMON_CLK
|
||||
depends on ARCH_MESON || COMPILE_TEST
|
||||
help
|
||||
This enables master mode support for the SPICC (SPI communication
|
||||
|
@ -443,6 +457,16 @@ config SPI_MT7621
|
|||
help
|
||||
This selects a driver for the MediaTek MT7621 SPI Controller.
|
||||
|
||||
config SPI_MTK_NOR
|
||||
tristate "MediaTek SPI NOR controller"
|
||||
depends on ARCH_MEDIATEK || COMPILE_TEST
|
||||
help
|
||||
This enables support for SPI NOR controller found on MediaTek
|
||||
ARM SoCs. This is a controller specifically for SPI-NOR flash.
|
||||
It can perform generic SPI transfers up to 6 bytes via generic
|
||||
SPI interface as well as several SPI-NOR specific instructions
|
||||
via SPI MEM interface.
|
||||
|
||||
config SPI_NPCM_FIU
|
||||
tristate "Nuvoton NPCM FLASH Interface Unit"
|
||||
depends on ARCH_NPCM || COMPILE_TEST
|
||||
|
@ -890,6 +914,17 @@ config SPI_ZYNQMP_GQSPI
|
|||
# Add new SPI master controllers in alphabetical order above this line
|
||||
#
|
||||
|
||||
comment "SPI Multiplexer support"
|
||||
|
||||
config SPI_MUX
|
||||
tristate "SPI multiplexer support"
|
||||
select MULTIPLEXER
|
||||
help
|
||||
This adds support for SPI multiplexers. Each SPI mux will be
|
||||
accessible as a SPI controller, the devices behind the mux will appear
|
||||
to be chip selects on this controller. It is still necessary to
|
||||
select one or more specific mux-controller drivers.
|
||||
|
||||
#
|
||||
# There are lots of SPI device types, with sensors and memory
|
||||
# being probably the most widely used ones.
|
||||
|
|
|
@ -9,11 +9,13 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
|
|||
# config declarations into driver model code
|
||||
obj-$(CONFIG_SPI_MASTER) += spi.o
|
||||
obj-$(CONFIG_SPI_MEM) += spi-mem.o
|
||||
obj-$(CONFIG_SPI_MUX) += spi-mux.o
|
||||
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
|
||||
obj-$(CONFIG_SPI_LOOPBACK_TEST) += spi-loopback-test.o
|
||||
|
||||
# SPI master controller drivers (bus)
|
||||
obj-$(CONFIG_SPI_ALTERA) += spi-altera.o
|
||||
obj-$(CONFIG_SPI_AR934X) += spi-ar934x.o
|
||||
obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o
|
||||
obj-$(CONFIG_SPI_ATMEL) += spi-atmel.o
|
||||
obj-$(CONFIG_SPI_ATMEL_QUADSPI) += atmel-quadspi.o
|
||||
|
@ -40,6 +42,7 @@ spi-dw-midpci-objs := spi-dw-pci.o spi-dw-mid.o
|
|||
obj-$(CONFIG_SPI_EFM32) += spi-efm32.o
|
||||
obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o
|
||||
obj-$(CONFIG_SPI_FALCON) += spi-falcon.o
|
||||
obj-$(CONFIG_SPI_FSI) += spi-fsi.o
|
||||
obj-$(CONFIG_SPI_FSL_CPM) += spi-fsl-cpm.o
|
||||
obj-$(CONFIG_SPI_FSL_DSPI) += spi-fsl-dspi.o
|
||||
obj-$(CONFIG_SPI_FSL_LIB) += spi-fsl-lib.o
|
||||
|
@ -62,6 +65,7 @@ obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o
|
|||
obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o
|
||||
obj-$(CONFIG_SPI_MT65XX) += spi-mt65xx.o
|
||||
obj-$(CONFIG_SPI_MT7621) += spi-mt7621.o
|
||||
obj-$(CONFIG_SPI_MTK_NOR) += spi-mtk-nor.o
|
||||
obj-$(CONFIG_SPI_MXIC) += spi-mxic.o
|
||||
obj-$(CONFIG_SPI_MXS) += spi-mxs.o
|
||||
obj-$(CONFIG_SPI_NPCM_FIU) += spi-npcm-fiu.o
|
||||
|
|
|
@ -173,6 +173,81 @@ static const struct atmel_qspi_mode atmel_qspi_modes[] = {
|
|||
{ 4, 4, 4, QSPI_IFR_WIDTH_QUAD_CMD },
|
||||
};
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
static const char *atmel_qspi_reg_name(u32 offset, char *tmp, size_t sz)
|
||||
{
|
||||
switch (offset) {
|
||||
case QSPI_CR:
|
||||
return "CR";
|
||||
case QSPI_MR:
|
||||
return "MR";
|
||||
case QSPI_RD:
|
||||
return "MR";
|
||||
case QSPI_TD:
|
||||
return "TD";
|
||||
case QSPI_SR:
|
||||
return "SR";
|
||||
case QSPI_IER:
|
||||
return "IER";
|
||||
case QSPI_IDR:
|
||||
return "IDR";
|
||||
case QSPI_IMR:
|
||||
return "IMR";
|
||||
case QSPI_SCR:
|
||||
return "SCR";
|
||||
case QSPI_IAR:
|
||||
return "IAR";
|
||||
case QSPI_ICR:
|
||||
return "ICR/WICR";
|
||||
case QSPI_IFR:
|
||||
return "IFR";
|
||||
case QSPI_RICR:
|
||||
return "RICR";
|
||||
case QSPI_SMR:
|
||||
return "SMR";
|
||||
case QSPI_SKR:
|
||||
return "SKR";
|
||||
case QSPI_WPMR:
|
||||
return "WPMR";
|
||||
case QSPI_WPSR:
|
||||
return "WPSR";
|
||||
case QSPI_VERSION:
|
||||
return "VERSION";
|
||||
default:
|
||||
snprintf(tmp, sz, "0x%02x", offset);
|
||||
break;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
#endif /* VERBOSE_DEBUG */
|
||||
|
||||
static u32 atmel_qspi_read(struct atmel_qspi *aq, u32 offset)
|
||||
{
|
||||
u32 value = readl_relaxed(aq->regs + offset);
|
||||
|
||||
#ifdef VERBOSE_DEBUG
|
||||
char tmp[8];
|
||||
|
||||
dev_vdbg(&aq->pdev->dev, "read 0x%08x from %s\n", value,
|
||||
atmel_qspi_reg_name(offset, tmp, sizeof(tmp)));
|
||||
#endif /* VERBOSE_DEBUG */
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void atmel_qspi_write(u32 value, struct atmel_qspi *aq, u32 offset)
|
||||
{
|
||||
#ifdef VERBOSE_DEBUG
|
||||
char tmp[8];
|
||||
|
||||
dev_vdbg(&aq->pdev->dev, "write 0x%08x into %s\n", value,
|
||||
atmel_qspi_reg_name(offset, tmp, sizeof(tmp)));
|
||||
#endif /* VERBOSE_DEBUG */
|
||||
|
||||
writel_relaxed(value, aq->regs + offset);
|
||||
}
|
||||
|
||||
static inline bool atmel_qspi_is_compatible(const struct spi_mem_op *op,
|
||||
const struct atmel_qspi_mode *mode)
|
||||
{
|
||||
|
@ -293,32 +368,32 @@ static int atmel_qspi_set_cfg(struct atmel_qspi *aq,
|
|||
* Serial Memory Mode (SMM).
|
||||
*/
|
||||
if (aq->mr != QSPI_MR_SMM) {
|
||||
writel_relaxed(QSPI_MR_SMM, aq->regs + QSPI_MR);
|
||||
atmel_qspi_write(QSPI_MR_SMM, aq, QSPI_MR);
|
||||
aq->mr = QSPI_MR_SMM;
|
||||
}
|
||||
|
||||
/* Clear pending interrupts */
|
||||
(void)readl_relaxed(aq->regs + QSPI_SR);
|
||||
(void)atmel_qspi_read(aq, QSPI_SR);
|
||||
|
||||
if (aq->caps->has_ricr) {
|
||||
if (!op->addr.nbytes && op->data.dir == SPI_MEM_DATA_IN)
|
||||
ifr |= QSPI_IFR_APBTFRTYP_READ;
|
||||
|
||||
/* Set QSPI Instruction Frame registers */
|
||||
writel_relaxed(iar, aq->regs + QSPI_IAR);
|
||||
atmel_qspi_write(iar, aq, QSPI_IAR);
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
writel_relaxed(icr, aq->regs + QSPI_RICR);
|
||||
atmel_qspi_write(icr, aq, QSPI_RICR);
|
||||
else
|
||||
writel_relaxed(icr, aq->regs + QSPI_WICR);
|
||||
writel_relaxed(ifr, aq->regs + QSPI_IFR);
|
||||
atmel_qspi_write(icr, aq, QSPI_WICR);
|
||||
atmel_qspi_write(ifr, aq, QSPI_IFR);
|
||||
} else {
|
||||
if (op->data.dir == SPI_MEM_DATA_OUT)
|
||||
ifr |= QSPI_IFR_SAMA5D2_WRITE_TRSFR;
|
||||
|
||||
/* Set QSPI Instruction Frame registers */
|
||||
writel_relaxed(iar, aq->regs + QSPI_IAR);
|
||||
writel_relaxed(icr, aq->regs + QSPI_ICR);
|
||||
writel_relaxed(ifr, aq->regs + QSPI_IFR);
|
||||
atmel_qspi_write(iar, aq, QSPI_IAR);
|
||||
atmel_qspi_write(icr, aq, QSPI_ICR);
|
||||
atmel_qspi_write(ifr, aq, QSPI_IFR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -345,7 +420,7 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|||
/* Skip to the final steps if there is no data */
|
||||
if (op->data.nbytes) {
|
||||
/* Dummy read of QSPI_IFR to synchronize APB and AHB accesses */
|
||||
(void)readl_relaxed(aq->regs + QSPI_IFR);
|
||||
(void)atmel_qspi_read(aq, QSPI_IFR);
|
||||
|
||||
/* Send/Receive data */
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
|
@ -356,22 +431,22 @@ static int atmel_qspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|||
op->data.nbytes);
|
||||
|
||||
/* Release the chip-select */
|
||||
writel_relaxed(QSPI_CR_LASTXFER, aq->regs + QSPI_CR);
|
||||
atmel_qspi_write(QSPI_CR_LASTXFER, aq, QSPI_CR);
|
||||
}
|
||||
|
||||
/* Poll INSTRuction End status */
|
||||
sr = readl_relaxed(aq->regs + QSPI_SR);
|
||||
sr = atmel_qspi_read(aq, QSPI_SR);
|
||||
if ((sr & QSPI_SR_CMD_COMPLETED) == QSPI_SR_CMD_COMPLETED)
|
||||
return err;
|
||||
|
||||
/* Wait for INSTRuction End interrupt */
|
||||
reinit_completion(&aq->cmd_completion);
|
||||
aq->pending = sr & QSPI_SR_CMD_COMPLETED;
|
||||
writel_relaxed(QSPI_SR_CMD_COMPLETED, aq->regs + QSPI_IER);
|
||||
atmel_qspi_write(QSPI_SR_CMD_COMPLETED, aq, QSPI_IER);
|
||||
if (!wait_for_completion_timeout(&aq->cmd_completion,
|
||||
msecs_to_jiffies(1000)))
|
||||
err = -ETIMEDOUT;
|
||||
writel_relaxed(QSPI_SR_CMD_COMPLETED, aq->regs + QSPI_IDR);
|
||||
atmel_qspi_write(QSPI_SR_CMD_COMPLETED, aq, QSPI_IDR);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
@ -410,7 +485,7 @@ static int atmel_qspi_setup(struct spi_device *spi)
|
|||
scbr--;
|
||||
|
||||
aq->scr = QSPI_SCR_SCBR(scbr);
|
||||
writel_relaxed(aq->scr, aq->regs + QSPI_SCR);
|
||||
atmel_qspi_write(aq->scr, aq, QSPI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -418,14 +493,14 @@ static int atmel_qspi_setup(struct spi_device *spi)
|
|||
static void atmel_qspi_init(struct atmel_qspi *aq)
|
||||
{
|
||||
/* Reset the QSPI controller */
|
||||
writel_relaxed(QSPI_CR_SWRST, aq->regs + QSPI_CR);
|
||||
atmel_qspi_write(QSPI_CR_SWRST, aq, QSPI_CR);
|
||||
|
||||
/* Set the QSPI controller by default in Serial Memory Mode */
|
||||
writel_relaxed(QSPI_MR_SMM, aq->regs + QSPI_MR);
|
||||
atmel_qspi_write(QSPI_MR_SMM, aq, QSPI_MR);
|
||||
aq->mr = QSPI_MR_SMM;
|
||||
|
||||
/* Enable the QSPI controller */
|
||||
writel_relaxed(QSPI_CR_QSPIEN, aq->regs + QSPI_CR);
|
||||
atmel_qspi_write(QSPI_CR_QSPIEN, aq, QSPI_CR);
|
||||
}
|
||||
|
||||
static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
|
||||
|
@ -433,8 +508,8 @@ static irqreturn_t atmel_qspi_interrupt(int irq, void *dev_id)
|
|||
struct atmel_qspi *aq = dev_id;
|
||||
u32 status, mask, pending;
|
||||
|
||||
status = readl_relaxed(aq->regs + QSPI_SR);
|
||||
mask = readl_relaxed(aq->regs + QSPI_IMR);
|
||||
status = atmel_qspi_read(aq, QSPI_SR);
|
||||
mask = atmel_qspi_read(aq, QSPI_IMR);
|
||||
pending = status & mask;
|
||||
|
||||
if (!pending)
|
||||
|
@ -569,7 +644,7 @@ static int atmel_qspi_remove(struct platform_device *pdev)
|
|||
struct atmel_qspi *aq = spi_controller_get_devdata(ctrl);
|
||||
|
||||
spi_unregister_controller(ctrl);
|
||||
writel_relaxed(QSPI_CR_QSPIDIS, aq->regs + QSPI_CR);
|
||||
atmel_qspi_write(QSPI_CR_QSPIDIS, aq, QSPI_CR);
|
||||
clk_disable_unprepare(aq->qspick);
|
||||
clk_disable_unprepare(aq->pclk);
|
||||
return 0;
|
||||
|
@ -596,7 +671,7 @@ static int __maybe_unused atmel_qspi_resume(struct device *dev)
|
|||
|
||||
atmel_qspi_init(aq);
|
||||
|
||||
writel_relaxed(aq->scr, aq->regs + QSPI_SCR);
|
||||
atmel_qspi_write(aq->scr, aq, QSPI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// SPI controller driver for Qualcomm Atheros AR934x/QCA95xx SoCs
|
||||
//
|
||||
// Copyright (C) 2020 Chuanhong Guo <gch981213@gmail.com>
|
||||
//
|
||||
// Based on spi-mt7621.c:
|
||||
// Copyright (C) 2011 Sergiy <piratfm@gmail.com>
|
||||
// Copyright (C) 2011-2013 Gabor Juhos <juhosg@openwrt.org>
|
||||
// Copyright (C) 2014-2015 Felix Fietkau <nbd@nbd.name>
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define DRIVER_NAME "spi-ar934x"
|
||||
|
||||
#define AR934X_SPI_REG_FS 0x00
|
||||
#define AR934X_SPI_ENABLE BIT(0)
|
||||
|
||||
#define AR934X_SPI_REG_IOC 0x08
|
||||
#define AR934X_SPI_IOC_INITVAL 0x70000
|
||||
|
||||
#define AR934X_SPI_REG_CTRL 0x04
|
||||
#define AR934X_SPI_CLK_MASK GENMASK(5, 0)
|
||||
|
||||
#define AR934X_SPI_DATAOUT 0x10
|
||||
|
||||
#define AR934X_SPI_REG_SHIFT_CTRL 0x14
|
||||
#define AR934X_SPI_SHIFT_EN BIT(31)
|
||||
#define AR934X_SPI_SHIFT_CS(n) BIT(28 + (n))
|
||||
#define AR934X_SPI_SHIFT_TERM 26
|
||||
#define AR934X_SPI_SHIFT_VAL(cs, term, count) \
|
||||
(AR934X_SPI_SHIFT_EN | AR934X_SPI_SHIFT_CS(cs) | \
|
||||
(term) << AR934X_SPI_SHIFT_TERM | (count))
|
||||
|
||||
#define AR934X_SPI_DATAIN 0x18
|
||||
|
||||
struct ar934x_spi {
|
||||
struct spi_controller *ctlr;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
unsigned int clk_freq;
|
||||
};
|
||||
|
||||
static inline int ar934x_spi_clk_div(struct ar934x_spi *sp, unsigned int freq)
|
||||
{
|
||||
int div = DIV_ROUND_UP(sp->clk_freq, freq * 2) - 1;
|
||||
|
||||
if (div < 0)
|
||||
return 0;
|
||||
else if (div > AR934X_SPI_CLK_MASK)
|
||||
return -EINVAL;
|
||||
else
|
||||
return div;
|
||||
}
|
||||
|
||||
static int ar934x_spi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct ar934x_spi *sp = spi_controller_get_devdata(spi->master);
|
||||
|
||||
if ((spi->max_speed_hz == 0) ||
|
||||
(spi->max_speed_hz > (sp->clk_freq / 2))) {
|
||||
spi->max_speed_hz = sp->clk_freq / 2;
|
||||
} else if (spi->max_speed_hz < (sp->clk_freq / 128)) {
|
||||
dev_err(&spi->dev, "spi clock is too low\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ar934x_spi_transfer_one_message(struct spi_controller *master,
|
||||
struct spi_message *m)
|
||||
{
|
||||
struct ar934x_spi *sp = spi_controller_get_devdata(master);
|
||||
struct spi_transfer *t = NULL;
|
||||
struct spi_device *spi = m->spi;
|
||||
unsigned long trx_done, trx_cur;
|
||||
int stat = 0;
|
||||
u8 term = 0;
|
||||
int div, i;
|
||||
u32 reg;
|
||||
const u8 *tx_buf;
|
||||
u8 *buf;
|
||||
|
||||
m->actual_length = 0;
|
||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||||
if (t->speed_hz)
|
||||
div = ar934x_spi_clk_div(sp, t->speed_hz);
|
||||
else
|
||||
div = ar934x_spi_clk_div(sp, spi->max_speed_hz);
|
||||
if (div < 0) {
|
||||
stat = -EIO;
|
||||
goto msg_done;
|
||||
}
|
||||
|
||||
reg = ioread32(sp->base + AR934X_SPI_REG_CTRL);
|
||||
reg &= ~AR934X_SPI_CLK_MASK;
|
||||
reg |= div;
|
||||
iowrite32(reg, sp->base + AR934X_SPI_REG_CTRL);
|
||||
iowrite32(0, sp->base + AR934X_SPI_DATAOUT);
|
||||
|
||||
for (trx_done = 0; trx_done < t->len; trx_done += 4) {
|
||||
trx_cur = t->len - trx_done;
|
||||
if (trx_cur > 4)
|
||||
trx_cur = 4;
|
||||
else if (list_is_last(&t->transfer_list, &m->transfers))
|
||||
term = 1;
|
||||
|
||||
if (t->tx_buf) {
|
||||
tx_buf = t->tx_buf + trx_done;
|
||||
reg = tx_buf[0];
|
||||
for (i = 1; i < trx_cur; i++)
|
||||
reg = reg << 8 | tx_buf[i];
|
||||
iowrite32(reg, sp->base + AR934X_SPI_DATAOUT);
|
||||
}
|
||||
|
||||
reg = AR934X_SPI_SHIFT_VAL(spi->chip_select, term,
|
||||
trx_cur * 8);
|
||||
iowrite32(reg, sp->base + AR934X_SPI_REG_SHIFT_CTRL);
|
||||
stat = readl_poll_timeout(
|
||||
sp->base + AR934X_SPI_REG_SHIFT_CTRL, reg,
|
||||
!(reg & AR934X_SPI_SHIFT_EN), 0, 5);
|
||||
if (stat < 0)
|
||||
goto msg_done;
|
||||
|
||||
if (t->rx_buf) {
|
||||
reg = ioread32(sp->base + AR934X_SPI_DATAIN);
|
||||
buf = t->rx_buf + trx_done;
|
||||
for (i = 0; i < trx_cur; i++) {
|
||||
buf[trx_cur - i - 1] = reg & 0xff;
|
||||
reg >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
m->actual_length += t->len;
|
||||
}
|
||||
|
||||
msg_done:
|
||||
m->status = stat;
|
||||
spi_finalize_current_message(master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ar934x_spi_match[] = {
|
||||
{ .compatible = "qca,ar934x-spi" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ar934x_spi_match);
|
||||
|
||||
static int ar934x_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *ctlr;
|
||||
struct ar934x_spi *sp;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctlr = spi_alloc_master(&pdev->dev, sizeof(*sp));
|
||||
if (!ctlr) {
|
||||
dev_info(&pdev->dev, "failed to allocate spi controller\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* disable flash mapping and expose spi controller registers */
|
||||
iowrite32(AR934X_SPI_ENABLE, base + AR934X_SPI_REG_FS);
|
||||
/* restore pins to default state: CSn=1 DO=CLK=0 */
|
||||
iowrite32(AR934X_SPI_IOC_INITVAL, base + AR934X_SPI_REG_IOC);
|
||||
|
||||
ctlr->mode_bits = SPI_LSB_FIRST;
|
||||
ctlr->setup = ar934x_spi_setup;
|
||||
ctlr->transfer_one_message = ar934x_spi_transfer_one_message;
|
||||
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
ctlr->dev.of_node = pdev->dev.of_node;
|
||||
ctlr->num_chipselect = 3;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, ctlr);
|
||||
|
||||
sp = spi_controller_get_devdata(ctlr);
|
||||
sp->base = base;
|
||||
sp->clk = clk;
|
||||
sp->clk_freq = clk_get_rate(clk);
|
||||
sp->ctlr = ctlr;
|
||||
|
||||
return devm_spi_register_controller(&pdev->dev, ctlr);
|
||||
}
|
||||
|
||||
static int ar934x_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *ctlr;
|
||||
struct ar934x_spi *sp;
|
||||
|
||||
ctlr = dev_get_drvdata(&pdev->dev);
|
||||
sp = spi_controller_get_devdata(ctlr);
|
||||
|
||||
clk_disable_unprepare(sp->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ar934x_spi_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = ar934x_spi_match,
|
||||
},
|
||||
.probe = ar934x_spi_probe,
|
||||
.remove = ar934x_spi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ar934x_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SPI controller driver for Qualcomm Atheros AR934x/QCA95xx");
|
||||
MODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
|
@ -6,14 +6,13 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_data/efm32-spi.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#define DRIVER_NAME "efm32-spi"
|
||||
|
||||
|
@ -82,9 +81,6 @@ struct efm32_spi_ddata {
|
|||
const u8 *tx_buf;
|
||||
u8 *rx_buf;
|
||||
unsigned tx_len, rx_len;
|
||||
|
||||
/* chip selects */
|
||||
unsigned csgpio[];
|
||||
};
|
||||
|
||||
#define ddata_to_dev(ddata) (&(ddata->bitbang.master->dev))
|
||||
|
@ -102,14 +98,6 @@ static u32 efm32_spi_read32(struct efm32_spi_ddata *ddata, unsigned offset)
|
|||
return readl_relaxed(ddata->base + offset);
|
||||
}
|
||||
|
||||
static void efm32_spi_chipselect(struct spi_device *spi, int is_on)
|
||||
{
|
||||
struct efm32_spi_ddata *ddata = spi_master_get_devdata(spi->master);
|
||||
int value = !(spi->mode & SPI_CS_HIGH) == !(is_on == BITBANG_CS_ACTIVE);
|
||||
|
||||
gpio_set_value(ddata->csgpio[spi->chip_select], value);
|
||||
}
|
||||
|
||||
static int efm32_spi_setup_transfer(struct spi_device *spi,
|
||||
struct spi_transfer *t)
|
||||
{
|
||||
|
@ -320,17 +308,11 @@ static int efm32_spi_probe(struct platform_device *pdev)
|
|||
int ret;
|
||||
struct spi_master *master;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int num_cs, i;
|
||||
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
num_cs = of_gpio_named_count(np, "cs-gpios");
|
||||
if (num_cs < 0)
|
||||
return num_cs;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev,
|
||||
sizeof(*ddata) + num_cs * sizeof(unsigned));
|
||||
master = spi_alloc_master(&pdev->dev, sizeof(*ddata));
|
||||
if (!master) {
|
||||
dev_dbg(&pdev->dev,
|
||||
"failed to allocate spi master controller\n");
|
||||
|
@ -340,14 +322,13 @@ static int efm32_spi_probe(struct platform_device *pdev)
|
|||
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
|
||||
master->num_chipselect = num_cs;
|
||||
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
|
||||
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16);
|
||||
master->use_gpio_descriptors = true;
|
||||
|
||||
ddata = spi_master_get_devdata(master);
|
||||
|
||||
ddata->bitbang.master = master;
|
||||
ddata->bitbang.chipselect = efm32_spi_chipselect;
|
||||
ddata->bitbang.setup_transfer = efm32_spi_setup_transfer;
|
||||
ddata->bitbang.txrx_bufs = efm32_spi_txrx_bufs;
|
||||
|
||||
|
@ -361,25 +342,6 @@ static int efm32_spi_probe(struct platform_device *pdev)
|
|||
goto err;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_cs; ++i) {
|
||||
ret = of_get_named_gpio(np, "cs-gpios", i);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to get csgpio#%u (%d)\n",
|
||||
i, ret);
|
||||
goto err;
|
||||
}
|
||||
ddata->csgpio[i] = ret;
|
||||
dev_dbg(&pdev->dev, "csgpio#%u = %u\n", i, ddata->csgpio[i]);
|
||||
ret = devm_gpio_request_one(&pdev->dev, ddata->csgpio[i],
|
||||
GPIOF_OUT_INIT_LOW, DRIVER_NAME);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to configure csgpio#%u (%d)\n",
|
||||
i, ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
ret = -ENODEV;
|
||||
|
|
|
@ -0,0 +1,558 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) IBM Corporation 2020
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/fsi.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define FSI_ENGID_SPI 0x23
|
||||
#define FSI_MBOX_ROOT_CTRL_8 0x2860
|
||||
|
||||
#define FSI2SPI_DATA0 0x00
|
||||
#define FSI2SPI_DATA1 0x04
|
||||
#define FSI2SPI_CMD 0x08
|
||||
#define FSI2SPI_CMD_WRITE BIT(31)
|
||||
#define FSI2SPI_RESET 0x18
|
||||
#define FSI2SPI_STATUS 0x1c
|
||||
#define FSI2SPI_STATUS_ANY_ERROR BIT(31)
|
||||
#define FSI2SPI_IRQ 0x20
|
||||
|
||||
#define SPI_FSI_BASE 0x70000
|
||||
#define SPI_FSI_INIT_TIMEOUT_MS 1000
|
||||
#define SPI_FSI_MAX_TRANSFER_SIZE 2048
|
||||
|
||||
#define SPI_FSI_ERROR 0x0
|
||||
#define SPI_FSI_COUNTER_CFG 0x1
|
||||
#define SPI_FSI_COUNTER_CFG_LOOPS(x) (((u64)(x) & 0xffULL) << 32)
|
||||
#define SPI_FSI_CFG1 0x2
|
||||
#define SPI_FSI_CLOCK_CFG 0x3
|
||||
#define SPI_FSI_CLOCK_CFG_MM_ENABLE BIT_ULL(32)
|
||||
#define SPI_FSI_CLOCK_CFG_ECC_DISABLE (BIT_ULL(35) | BIT_ULL(33))
|
||||
#define SPI_FSI_CLOCK_CFG_RESET1 (BIT_ULL(36) | BIT_ULL(38))
|
||||
#define SPI_FSI_CLOCK_CFG_RESET2 (BIT_ULL(37) | BIT_ULL(39))
|
||||
#define SPI_FSI_CLOCK_CFG_MODE (BIT_ULL(41) | BIT_ULL(42))
|
||||
#define SPI_FSI_CLOCK_CFG_SCK_RECV_DEL GENMASK_ULL(51, 44)
|
||||
#define SPI_FSI_CLOCK_CFG_SCK_NO_DEL BIT_ULL(51)
|
||||
#define SPI_FSI_CLOCK_CFG_SCK_DIV GENMASK_ULL(63, 52)
|
||||
#define SPI_FSI_MMAP 0x4
|
||||
#define SPI_FSI_DATA_TX 0x5
|
||||
#define SPI_FSI_DATA_RX 0x6
|
||||
#define SPI_FSI_SEQUENCE 0x7
|
||||
#define SPI_FSI_SEQUENCE_STOP 0x00
|
||||
#define SPI_FSI_SEQUENCE_SEL_SLAVE(x) (0x10 | ((x) & 0xf))
|
||||
#define SPI_FSI_SEQUENCE_SHIFT_OUT(x) (0x30 | ((x) & 0xf))
|
||||
#define SPI_FSI_SEQUENCE_SHIFT_IN(x) (0x40 | ((x) & 0xf))
|
||||
#define SPI_FSI_SEQUENCE_COPY_DATA_TX 0xc0
|
||||
#define SPI_FSI_SEQUENCE_BRANCH(x) (0xe0 | ((x) & 0xf))
|
||||
#define SPI_FSI_STATUS 0x8
|
||||
#define SPI_FSI_STATUS_ERROR \
|
||||
(GENMASK_ULL(31, 21) | GENMASK_ULL(15, 12))
|
||||
#define SPI_FSI_STATUS_SEQ_STATE GENMASK_ULL(55, 48)
|
||||
#define SPI_FSI_STATUS_SEQ_STATE_IDLE BIT_ULL(48)
|
||||
#define SPI_FSI_STATUS_TDR_UNDERRUN BIT_ULL(57)
|
||||
#define SPI_FSI_STATUS_TDR_OVERRUN BIT_ULL(58)
|
||||
#define SPI_FSI_STATUS_TDR_FULL BIT_ULL(59)
|
||||
#define SPI_FSI_STATUS_RDR_UNDERRUN BIT_ULL(61)
|
||||
#define SPI_FSI_STATUS_RDR_OVERRUN BIT_ULL(62)
|
||||
#define SPI_FSI_STATUS_RDR_FULL BIT_ULL(63)
|
||||
#define SPI_FSI_STATUS_ANY_ERROR \
|
||||
(SPI_FSI_STATUS_ERROR | SPI_FSI_STATUS_TDR_UNDERRUN | \
|
||||
SPI_FSI_STATUS_TDR_OVERRUN | SPI_FSI_STATUS_RDR_UNDERRUN | \
|
||||
SPI_FSI_STATUS_RDR_OVERRUN)
|
||||
#define SPI_FSI_PORT_CTRL 0x9
|
||||
|
||||
struct fsi_spi {
|
||||
struct device *dev; /* SPI controller device */
|
||||
struct fsi_device *fsi; /* FSI2SPI CFAM engine device */
|
||||
u32 base;
|
||||
};
|
||||
|
||||
struct fsi_spi_sequence {
|
||||
int bit;
|
||||
u64 data;
|
||||
};
|
||||
|
||||
static int fsi_spi_check_status(struct fsi_spi *ctx)
|
||||
{
|
||||
int rc;
|
||||
u32 sts;
|
||||
__be32 sts_be;
|
||||
|
||||
rc = fsi_device_read(ctx->fsi, FSI2SPI_STATUS, &sts_be,
|
||||
sizeof(sts_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
sts = be32_to_cpu(sts_be);
|
||||
if (sts & FSI2SPI_STATUS_ANY_ERROR) {
|
||||
dev_err(ctx->dev, "Error with FSI2SPI interface: %08x.\n", sts);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsi_spi_read_reg(struct fsi_spi *ctx, u32 offset, u64 *value)
|
||||
{
|
||||
int rc;
|
||||
__be32 cmd_be;
|
||||
__be32 data_be;
|
||||
u32 cmd = offset + ctx->base;
|
||||
|
||||
*value = 0ULL;
|
||||
|
||||
if (cmd & FSI2SPI_CMD_WRITE)
|
||||
return -EINVAL;
|
||||
|
||||
cmd_be = cpu_to_be32(cmd);
|
||||
rc = fsi_device_write(ctx->fsi, FSI2SPI_CMD, &cmd_be, sizeof(cmd_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = fsi_spi_check_status(ctx);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = fsi_device_read(ctx->fsi, FSI2SPI_DATA0, &data_be,
|
||||
sizeof(data_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
*value |= (u64)be32_to_cpu(data_be) << 32;
|
||||
|
||||
rc = fsi_device_read(ctx->fsi, FSI2SPI_DATA1, &data_be,
|
||||
sizeof(data_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
*value |= (u64)be32_to_cpu(data_be);
|
||||
dev_dbg(ctx->dev, "Read %02x[%016llx].\n", offset, *value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsi_spi_write_reg(struct fsi_spi *ctx, u32 offset, u64 value)
|
||||
{
|
||||
int rc;
|
||||
__be32 cmd_be;
|
||||
__be32 data_be;
|
||||
u32 cmd = offset + ctx->base;
|
||||
|
||||
if (cmd & FSI2SPI_CMD_WRITE)
|
||||
return -EINVAL;
|
||||
|
||||
dev_dbg(ctx->dev, "Write %02x[%016llx].\n", offset, value);
|
||||
|
||||
data_be = cpu_to_be32(upper_32_bits(value));
|
||||
rc = fsi_device_write(ctx->fsi, FSI2SPI_DATA0, &data_be,
|
||||
sizeof(data_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
data_be = cpu_to_be32(lower_32_bits(value));
|
||||
rc = fsi_device_write(ctx->fsi, FSI2SPI_DATA1, &data_be,
|
||||
sizeof(data_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
cmd_be = cpu_to_be32(cmd | FSI2SPI_CMD_WRITE);
|
||||
rc = fsi_device_write(ctx->fsi, FSI2SPI_CMD, &cmd_be, sizeof(cmd_be));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return fsi_spi_check_status(ctx);
|
||||
}
|
||||
|
||||
static int fsi_spi_data_in(u64 in, u8 *rx, int len)
|
||||
{
|
||||
int i;
|
||||
int num_bytes = min(len, 8);
|
||||
|
||||
for (i = 0; i < num_bytes; ++i)
|
||||
rx[i] = (u8)(in >> (8 * ((num_bytes - 1) - i)));
|
||||
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
static int fsi_spi_data_out(u64 *out, const u8 *tx, int len)
|
||||
{
|
||||
int i;
|
||||
int num_bytes = min(len, 8);
|
||||
u8 *out_bytes = (u8 *)out;
|
||||
|
||||
/* Unused bytes of the tx data should be 0. */
|
||||
*out = 0ULL;
|
||||
|
||||
for (i = 0; i < num_bytes; ++i)
|
||||
out_bytes[8 - (i + 1)] = tx[i];
|
||||
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
static int fsi_spi_reset(struct fsi_spi *ctx)
|
||||
{
|
||||
int rc;
|
||||
|
||||
dev_dbg(ctx->dev, "Resetting SPI controller.\n");
|
||||
|
||||
rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
|
||||
SPI_FSI_CLOCK_CFG_RESET1);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
|
||||
SPI_FSI_CLOCK_CFG_RESET2);
|
||||
}
|
||||
|
||||
static int fsi_spi_sequence_add(struct fsi_spi_sequence *seq, u8 val)
|
||||
{
|
||||
/*
|
||||
* Add the next byte of instruction to the 8-byte sequence register.
|
||||
* Then decrement the counter so that the next instruction will go in
|
||||
* the right place. Return the number of "slots" left in the sequence
|
||||
* register.
|
||||
*/
|
||||
seq->data |= (u64)val << seq->bit;
|
||||
seq->bit -= 8;
|
||||
|
||||
return ((64 - seq->bit) / 8) - 2;
|
||||
}
|
||||
|
||||
static void fsi_spi_sequence_init(struct fsi_spi_sequence *seq)
|
||||
{
|
||||
seq->bit = 56;
|
||||
seq->data = 0ULL;
|
||||
}
|
||||
|
||||
static int fsi_spi_sequence_transfer(struct fsi_spi *ctx,
|
||||
struct fsi_spi_sequence *seq,
|
||||
struct spi_transfer *transfer)
|
||||
{
|
||||
int loops;
|
||||
int idx;
|
||||
int rc;
|
||||
u8 len = min(transfer->len, 8U);
|
||||
u8 rem = transfer->len % len;
|
||||
|
||||
loops = transfer->len / len;
|
||||
|
||||
if (transfer->tx_buf) {
|
||||
idx = fsi_spi_sequence_add(seq,
|
||||
SPI_FSI_SEQUENCE_SHIFT_OUT(len));
|
||||
if (rem)
|
||||
rem = SPI_FSI_SEQUENCE_SHIFT_OUT(rem);
|
||||
} else if (transfer->rx_buf) {
|
||||
idx = fsi_spi_sequence_add(seq,
|
||||
SPI_FSI_SEQUENCE_SHIFT_IN(len));
|
||||
if (rem)
|
||||
rem = SPI_FSI_SEQUENCE_SHIFT_IN(rem);
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (loops > 1) {
|
||||
fsi_spi_sequence_add(seq, SPI_FSI_SEQUENCE_BRANCH(idx));
|
||||
|
||||
if (rem)
|
||||
fsi_spi_sequence_add(seq, rem);
|
||||
|
||||
rc = fsi_spi_write_reg(ctx, SPI_FSI_COUNTER_CFG,
|
||||
SPI_FSI_COUNTER_CFG_LOOPS(loops - 1));
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsi_spi_transfer_data(struct fsi_spi *ctx,
|
||||
struct spi_transfer *transfer)
|
||||
{
|
||||
int rc = 0;
|
||||
u64 status = 0ULL;
|
||||
|
||||
if (transfer->tx_buf) {
|
||||
int nb;
|
||||
int sent = 0;
|
||||
u64 out = 0ULL;
|
||||
const u8 *tx = transfer->tx_buf;
|
||||
|
||||
while (transfer->len > sent) {
|
||||
nb = fsi_spi_data_out(&out, &tx[sent],
|
||||
(int)transfer->len - sent);
|
||||
|
||||
rc = fsi_spi_write_reg(ctx, SPI_FSI_DATA_TX, out);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
do {
|
||||
rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS,
|
||||
&status);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (status & SPI_FSI_STATUS_ANY_ERROR) {
|
||||
rc = fsi_spi_reset(ctx);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
} while (status & SPI_FSI_STATUS_TDR_FULL);
|
||||
|
||||
sent += nb;
|
||||
}
|
||||
} else if (transfer->rx_buf) {
|
||||
int recv = 0;
|
||||
u64 in = 0ULL;
|
||||
u8 *rx = transfer->rx_buf;
|
||||
|
||||
while (transfer->len > recv) {
|
||||
do {
|
||||
rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS,
|
||||
&status);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (status & SPI_FSI_STATUS_ANY_ERROR) {
|
||||
rc = fsi_spi_reset(ctx);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
return -EREMOTEIO;
|
||||
}
|
||||
} while (!(status & SPI_FSI_STATUS_RDR_FULL));
|
||||
|
||||
rc = fsi_spi_read_reg(ctx, SPI_FSI_DATA_RX, &in);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
recv += fsi_spi_data_in(in, &rx[recv],
|
||||
(int)transfer->len - recv);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsi_spi_transfer_init(struct fsi_spi *ctx)
|
||||
{
|
||||
int rc;
|
||||
bool reset = false;
|
||||
unsigned long end;
|
||||
u64 seq_state;
|
||||
u64 clock_cfg = 0ULL;
|
||||
u64 status = 0ULL;
|
||||
u64 wanted_clock_cfg = SPI_FSI_CLOCK_CFG_ECC_DISABLE |
|
||||
SPI_FSI_CLOCK_CFG_SCK_NO_DEL |
|
||||
FIELD_PREP(SPI_FSI_CLOCK_CFG_SCK_DIV, 4);
|
||||
|
||||
end = jiffies + msecs_to_jiffies(SPI_FSI_INIT_TIMEOUT_MS);
|
||||
do {
|
||||
if (time_after(jiffies, end))
|
||||
return -ETIMEDOUT;
|
||||
|
||||
rc = fsi_spi_read_reg(ctx, SPI_FSI_STATUS, &status);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
seq_state = status & SPI_FSI_STATUS_SEQ_STATE;
|
||||
|
||||
if (status & (SPI_FSI_STATUS_ANY_ERROR |
|
||||
SPI_FSI_STATUS_TDR_FULL |
|
||||
SPI_FSI_STATUS_RDR_FULL)) {
|
||||
if (reset)
|
||||
return -EIO;
|
||||
|
||||
rc = fsi_spi_reset(ctx);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
reset = true;
|
||||
continue;
|
||||
}
|
||||
} while (seq_state && (seq_state != SPI_FSI_STATUS_SEQ_STATE_IDLE));
|
||||
|
||||
rc = fsi_spi_read_reg(ctx, SPI_FSI_CLOCK_CFG, &clock_cfg);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if ((clock_cfg & (SPI_FSI_CLOCK_CFG_MM_ENABLE |
|
||||
SPI_FSI_CLOCK_CFG_ECC_DISABLE |
|
||||
SPI_FSI_CLOCK_CFG_MODE |
|
||||
SPI_FSI_CLOCK_CFG_SCK_RECV_DEL |
|
||||
SPI_FSI_CLOCK_CFG_SCK_DIV)) != wanted_clock_cfg)
|
||||
rc = fsi_spi_write_reg(ctx, SPI_FSI_CLOCK_CFG,
|
||||
wanted_clock_cfg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fsi_spi_transfer_one_message(struct spi_controller *ctlr,
|
||||
struct spi_message *mesg)
|
||||
{
|
||||
int rc = 0;
|
||||
u8 seq_slave = SPI_FSI_SEQUENCE_SEL_SLAVE(mesg->spi->chip_select + 1);
|
||||
struct spi_transfer *transfer;
|
||||
struct fsi_spi *ctx = spi_controller_get_devdata(ctlr);
|
||||
|
||||
list_for_each_entry(transfer, &mesg->transfers, transfer_list) {
|
||||
struct fsi_spi_sequence seq;
|
||||
struct spi_transfer *next = NULL;
|
||||
|
||||
/* Sequencer must do shift out (tx) first. */
|
||||
if (!transfer->tx_buf ||
|
||||
transfer->len > SPI_FSI_MAX_TRANSFER_SIZE) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_dbg(ctx->dev, "Start tx of %d bytes.\n", transfer->len);
|
||||
|
||||
rc = fsi_spi_transfer_init(ctx);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
fsi_spi_sequence_init(&seq);
|
||||
fsi_spi_sequence_add(&seq, seq_slave);
|
||||
|
||||
rc = fsi_spi_sequence_transfer(ctx, &seq, transfer);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
if (!list_is_last(&transfer->transfer_list,
|
||||
&mesg->transfers)) {
|
||||
next = list_next_entry(transfer, transfer_list);
|
||||
|
||||
/* Sequencer can only do shift in (rx) after tx. */
|
||||
if (next->rx_buf) {
|
||||
if (next->len > SPI_FSI_MAX_TRANSFER_SIZE) {
|
||||
rc = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
dev_dbg(ctx->dev, "Sequence rx of %d bytes.\n",
|
||||
next->len);
|
||||
|
||||
rc = fsi_spi_sequence_transfer(ctx, &seq,
|
||||
next);
|
||||
if (rc)
|
||||
goto error;
|
||||
} else {
|
||||
next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
fsi_spi_sequence_add(&seq, SPI_FSI_SEQUENCE_SEL_SLAVE(0));
|
||||
|
||||
rc = fsi_spi_write_reg(ctx, SPI_FSI_SEQUENCE, seq.data);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = fsi_spi_transfer_data(ctx, transfer);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
if (next) {
|
||||
rc = fsi_spi_transfer_data(ctx, next);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
transfer = next;
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
mesg->status = rc;
|
||||
spi_finalize_current_message(ctlr);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static size_t fsi_spi_max_transfer_size(struct spi_device *spi)
|
||||
{
|
||||
return SPI_FSI_MAX_TRANSFER_SIZE;
|
||||
}
|
||||
|
||||
static int fsi_spi_probe(struct device *dev)
|
||||
{
|
||||
int rc;
|
||||
u32 root_ctrl_8;
|
||||
struct device_node *np;
|
||||
int num_controllers_registered = 0;
|
||||
struct fsi_device *fsi = to_fsi_dev(dev);
|
||||
|
||||
/*
|
||||
* Check the SPI mux before attempting to probe. If the mux isn't set
|
||||
* then the SPI controllers can't access their slave devices.
|
||||
*/
|
||||
rc = fsi_slave_read(fsi->slave, FSI_MBOX_ROOT_CTRL_8, &root_ctrl_8,
|
||||
sizeof(root_ctrl_8));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (!root_ctrl_8) {
|
||||
dev_dbg(dev, "SPI mux not set, aborting probe.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for_each_available_child_of_node(dev->of_node, np) {
|
||||
u32 base;
|
||||
struct fsi_spi *ctx;
|
||||
struct spi_controller *ctlr;
|
||||
|
||||
if (of_property_read_u32(np, "reg", &base))
|
||||
continue;
|
||||
|
||||
ctlr = spi_alloc_master(dev, sizeof(*ctx));
|
||||
if (!ctlr)
|
||||
break;
|
||||
|
||||
ctlr->dev.of_node = np;
|
||||
ctlr->num_chipselect = of_get_available_child_count(np) ?: 1;
|
||||
ctlr->flags = SPI_CONTROLLER_HALF_DUPLEX;
|
||||
ctlr->max_transfer_size = fsi_spi_max_transfer_size;
|
||||
ctlr->transfer_one_message = fsi_spi_transfer_one_message;
|
||||
|
||||
ctx = spi_controller_get_devdata(ctlr);
|
||||
ctx->dev = &ctlr->dev;
|
||||
ctx->fsi = fsi;
|
||||
ctx->base = base + SPI_FSI_BASE;
|
||||
|
||||
rc = devm_spi_register_controller(dev, ctlr);
|
||||
if (rc)
|
||||
spi_controller_put(ctlr);
|
||||
else
|
||||
num_controllers_registered++;
|
||||
}
|
||||
|
||||
if (!num_controllers_registered)
|
||||
return -ENODEV;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct fsi_device_id fsi_spi_ids[] = {
|
||||
{ FSI_ENGID_SPI, FSI_VERSION_ANY },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(fsi, fsi_spi_ids);
|
||||
|
||||
static struct fsi_driver fsi_spi_driver = {
|
||||
.id_table = fsi_spi_ids,
|
||||
.drv = {
|
||||
.name = "spi-fsi",
|
||||
.bus = &fsi_bus_type,
|
||||
.probe = fsi_spi_probe,
|
||||
},
|
||||
};
|
||||
module_fsi_driver(fsi_spi_driver);
|
||||
|
||||
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
|
||||
MODULE_DESCRIPTION("FSI attached SPI controller");
|
||||
MODULE_LICENSE("GPL");
|
File diff suppressed because it is too large
Load Diff
|
@ -86,8 +86,6 @@
|
|||
#define TCR_RXMSK BIT(19)
|
||||
#define TCR_TXMSK BIT(18)
|
||||
|
||||
static int clkdivs[] = {1, 2, 4, 8, 16, 32, 64, 128};
|
||||
|
||||
struct lpspi_config {
|
||||
u8 bpw;
|
||||
u8 chip_select;
|
||||
|
@ -125,7 +123,7 @@ struct fsl_lpspi_data {
|
|||
struct completion dma_rx_completion;
|
||||
struct completion dma_tx_completion;
|
||||
|
||||
int chipselect[0];
|
||||
int chipselect[];
|
||||
};
|
||||
|
||||
static const struct of_device_id fsl_lpspi_dt_ids[] = {
|
||||
|
@ -331,15 +329,14 @@ static int fsl_lpspi_set_bitrate(struct fsl_lpspi_data *fsl_lpspi)
|
|||
}
|
||||
|
||||
for (prescale = 0; prescale < 8; prescale++) {
|
||||
scldiv = perclk_rate /
|
||||
(clkdivs[prescale] * config.speed_hz) - 2;
|
||||
scldiv = perclk_rate / config.speed_hz / (1 << prescale) - 2;
|
||||
if (scldiv < 256) {
|
||||
fsl_lpspi->config.prescale = prescale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prescale == 8 && scldiv >= 256)
|
||||
if (scldiv >= 256)
|
||||
return -EINVAL;
|
||||
|
||||
writel(scldiv | (scldiv << 8) | ((scldiv >> 1) << 16),
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/qcom-geni-se.h>
|
||||
|
@ -536,6 +535,7 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
struct spi_geni_master *mas;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
|
@ -545,28 +545,25 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
clk = devm_clk_get(&pdev->dev, "se");
|
||||
if (IS_ERR(clk)) {
|
||||
dev_err(&pdev->dev, "Err getting SE Core clk %ld\n",
|
||||
PTR_ERR(clk));
|
||||
clk = devm_clk_get(dev, "se");
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
|
||||
spi = spi_alloc_master(&pdev->dev, sizeof(*mas));
|
||||
spi = spi_alloc_master(dev, sizeof(*mas));
|
||||
if (!spi)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, spi);
|
||||
mas = spi_master_get_devdata(spi);
|
||||
mas->irq = irq;
|
||||
mas->dev = &pdev->dev;
|
||||
mas->se.dev = &pdev->dev;
|
||||
mas->se.wrapper = dev_get_drvdata(pdev->dev.parent);
|
||||
mas->dev = dev;
|
||||
mas->se.dev = dev;
|
||||
mas->se.wrapper = dev_get_drvdata(dev->parent);
|
||||
mas->se.base = base;
|
||||
mas->se.clk = clk;
|
||||
|
||||
spi->bus_num = -1;
|
||||
spi->dev.of_node = pdev->dev.of_node;
|
||||
spi->dev.of_node = dev->of_node;
|
||||
spi->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_CS_HIGH;
|
||||
spi->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
|
||||
spi->num_chipselect = 4;
|
||||
|
@ -579,14 +576,13 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
|
||||
init_completion(&mas->xfer_done);
|
||||
spin_lock_init(&mas->lock);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
ret = spi_geni_init(mas);
|
||||
if (ret)
|
||||
goto spi_geni_probe_runtime_disable;
|
||||
|
||||
ret = request_irq(mas->irq, geni_spi_isr,
|
||||
IRQF_TRIGGER_HIGH, "spi_geni", spi);
|
||||
ret = request_irq(mas->irq, geni_spi_isr, 0, dev_name(dev), spi);
|
||||
if (ret)
|
||||
goto spi_geni_probe_runtime_disable;
|
||||
|
||||
|
@ -598,7 +594,7 @@ static int spi_geni_probe(struct platform_device *pdev)
|
|||
spi_geni_probe_free_irq:
|
||||
free_irq(mas->irq, spi);
|
||||
spi_geni_probe_runtime_disable:
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
pm_runtime_disable(dev);
|
||||
spi_master_put(spi);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
@ -17,6 +18,12 @@
|
|||
#define HISI_SFC_V3XX_VERSION (0x1f8)
|
||||
|
||||
#define HISI_SFC_V3XX_CMD_CFG (0x300)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_DUAL_IN_DUAL_OUT (1 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_DUAL_IO (2 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_FULL_DIO (3 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_QUAD_IN_QUAD_OUT (5 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_QUAD_IO (6 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_FULL_QIO (7 << 17)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF 9
|
||||
#define HISI_SFC_V3XX_CMD_CFG_RW_MSK BIT(8)
|
||||
#define HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK BIT(7)
|
||||
|
@ -161,6 +168,43 @@ static int hisi_sfc_v3xx_generic_exec_op(struct hisi_sfc_v3xx_host *host,
|
|||
if (op->addr.nbytes)
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_ADDR_EN_MSK;
|
||||
|
||||
switch (op->data.buswidth) {
|
||||
case 0 ... 1:
|
||||
break;
|
||||
case 2:
|
||||
if (op->addr.buswidth <= 1) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_DUAL_IN_DUAL_OUT;
|
||||
} else if (op->addr.buswidth == 2) {
|
||||
if (op->cmd.buswidth <= 1) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_DUAL_IO;
|
||||
} else if (op->cmd.buswidth == 2) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_FULL_DIO;
|
||||
} else {
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
return -EIO;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (op->addr.buswidth <= 1) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_QUAD_IN_QUAD_OUT;
|
||||
} else if (op->addr.buswidth == 4) {
|
||||
if (op->cmd.buswidth <= 1) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_QUAD_IO;
|
||||
} else if (op->cmd.buswidth == 4) {
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_FULL_QIO;
|
||||
} else {
|
||||
return -EIO;
|
||||
}
|
||||
} else {
|
||||
return -EIO;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (op->data.dir != SPI_MEM_NO_DATA) {
|
||||
config |= (len - 1) << HISI_SFC_V3XX_CMD_CFG_DATA_CNT_OFF;
|
||||
config |= HISI_SFC_V3XX_CMD_CFG_DATA_EN_MSK;
|
||||
|
@ -207,6 +251,44 @@ static const struct spi_controller_mem_ops hisi_sfc_v3xx_mem_ops = {
|
|||
.exec_op = hisi_sfc_v3xx_exec_op,
|
||||
};
|
||||
|
||||
static int hisi_sfc_v3xx_buswidth_override_bits;
|
||||
|
||||
/*
|
||||
* ACPI FW does not allow us to currently set the device buswidth, so quirk it
|
||||
* depending on the board.
|
||||
*/
|
||||
static int __init hisi_sfc_v3xx_dmi_quirk(const struct dmi_system_id *d)
|
||||
{
|
||||
hisi_sfc_v3xx_buswidth_override_bits = SPI_RX_QUAD | SPI_TX_QUAD;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dmi_system_id hisi_sfc_v3xx_dmi_quirk_table[] = {
|
||||
{
|
||||
.callback = hisi_sfc_v3xx_dmi_quirk,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Huawei"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "D06"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = hisi_sfc_v3xx_dmi_quirk,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Huawei"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TaiShan 2280 V2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.callback = hisi_sfc_v3xx_dmi_quirk,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Huawei"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TaiShan 200 (Model 2280)"),
|
||||
},
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int hisi_sfc_v3xx_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -222,6 +304,8 @@ static int hisi_sfc_v3xx_probe(struct platform_device *pdev)
|
|||
ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD |
|
||||
SPI_TX_DUAL | SPI_TX_QUAD;
|
||||
|
||||
ctlr->buswidth_override_bits = hisi_sfc_v3xx_buswidth_override_bits;
|
||||
|
||||
host = spi_controller_get_devdata(ctlr);
|
||||
host->dev = dev;
|
||||
|
||||
|
@ -277,7 +361,20 @@ static struct platform_driver hisi_sfc_v3xx_spi_driver = {
|
|||
.probe = hisi_sfc_v3xx_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(hisi_sfc_v3xx_spi_driver);
|
||||
static int __init hisi_sfc_v3xx_spi_init(void)
|
||||
{
|
||||
dmi_check_system(hisi_sfc_v3xx_dmi_quirk_table);
|
||||
|
||||
return platform_driver_register(&hisi_sfc_v3xx_spi_driver);
|
||||
}
|
||||
|
||||
static void __exit hisi_sfc_v3xx_spi_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&hisi_sfc_v3xx_spi_driver);
|
||||
}
|
||||
|
||||
module_init(hisi_sfc_v3xx_spi_init);
|
||||
module_exit(hisi_sfc_v3xx_spi_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("John Garry <john.garry@huawei.com>");
|
||||
|
|
|
@ -418,12 +418,13 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
|||
struct spi_controller *ctlr = mem->spi->controller;
|
||||
size_t len;
|
||||
|
||||
len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes;
|
||||
|
||||
if (ctlr->mem_ops && ctlr->mem_ops->adjust_op_size)
|
||||
return ctlr->mem_ops->adjust_op_size(mem, op);
|
||||
|
||||
if (!ctlr->mem_ops || !ctlr->mem_ops->exec_op) {
|
||||
len = sizeof(op->cmd.opcode) + op->addr.nbytes +
|
||||
op->dummy.nbytes;
|
||||
|
||||
if (len > spi_max_transfer_size(mem->spi))
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -487,7 +488,7 @@ static ssize_t spi_mem_no_dirmap_write(struct spi_mem_dirmap_desc *desc,
|
|||
* This function is creating a direct mapping descriptor which can then be used
|
||||
* to access the memory using spi_mem_dirmap_read() or spi_mem_dirmap_write().
|
||||
* If the SPI controller driver does not support direct mapping, this function
|
||||
* fallback to an implementation using spi_mem_exec_op(), so that the caller
|
||||
* falls back to an implementation using spi_mem_exec_op(), so that the caller
|
||||
* doesn't have to bother implementing a fallback on his own.
|
||||
*
|
||||
* Return: a valid pointer in case of success, and ERR_PTR() otherwise.
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/types.h>
|
||||
|
@ -33,7 +35,6 @@
|
|||
* to have a CS go down over the full transfer
|
||||
*/
|
||||
|
||||
#define SPICC_MAX_FREQ 30000000
|
||||
#define SPICC_MAX_BURST 128
|
||||
|
||||
/* Register Map */
|
||||
|
@ -105,7 +106,21 @@
|
|||
#define SPICC_SWAP_RO BIT(14) /* RX FIFO Data Swap Read-Only */
|
||||
#define SPICC_SWAP_W1 BIT(15) /* RX FIFO Data Swap Write-Only */
|
||||
#define SPICC_DLYCTL_RO_MASK GENMASK(20, 15) /* Delay Control Read-Only */
|
||||
#define SPICC_DLYCTL_W1_MASK GENMASK(21, 16) /* Delay Control Write-Only */
|
||||
#define SPICC_MO_DELAY_MASK GENMASK(17, 16) /* Master Output Delay */
|
||||
#define SPICC_MO_NO_DELAY 0
|
||||
#define SPICC_MO_DELAY_1_CYCLE 1
|
||||
#define SPICC_MO_DELAY_2_CYCLE 2
|
||||
#define SPICC_MO_DELAY_3_CYCLE 3
|
||||
#define SPICC_MI_DELAY_MASK GENMASK(19, 18) /* Master Input Delay */
|
||||
#define SPICC_MI_NO_DELAY 0
|
||||
#define SPICC_MI_DELAY_1_CYCLE 1
|
||||
#define SPICC_MI_DELAY_2_CYCLE 2
|
||||
#define SPICC_MI_DELAY_3_CYCLE 3
|
||||
#define SPICC_MI_CAP_DELAY_MASK GENMASK(21, 20) /* Master Capture Delay */
|
||||
#define SPICC_CAP_AHEAD_2_CYCLE 0
|
||||
#define SPICC_CAP_AHEAD_1_CYCLE 1
|
||||
#define SPICC_CAP_NO_DELAY 2
|
||||
#define SPICC_CAP_DELAY_1_CYCLE 3
|
||||
#define SPICC_FIFORST_RO_MASK GENMASK(22, 21) /* FIFO Softreset Read-Only */
|
||||
#define SPICC_FIFORST_W1_MASK GENMASK(23, 22) /* FIFO Softreset Write-Only */
|
||||
|
||||
|
@ -113,31 +128,59 @@
|
|||
|
||||
#define SPICC_DWADDR 0x24 /* Write Address of DMA */
|
||||
|
||||
#define SPICC_ENH_CTL0 0x38 /* Enhanced Feature */
|
||||
#define SPICC_ENH_CLK_CS_DELAY_MASK GENMASK(15, 0)
|
||||
#define SPICC_ENH_DATARATE_MASK GENMASK(23, 16)
|
||||
#define SPICC_ENH_DATARATE_EN BIT(24)
|
||||
#define SPICC_ENH_MOSI_OEN BIT(25)
|
||||
#define SPICC_ENH_CLK_OEN BIT(26)
|
||||
#define SPICC_ENH_CS_OEN BIT(27)
|
||||
#define SPICC_ENH_CLK_CS_DELAY_EN BIT(28)
|
||||
#define SPICC_ENH_MAIN_CLK_AO BIT(29)
|
||||
|
||||
#define writel_bits_relaxed(mask, val, addr) \
|
||||
writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr)
|
||||
|
||||
#define SPICC_BURST_MAX 16
|
||||
#define SPICC_FIFO_HALF 10
|
||||
struct meson_spicc_data {
|
||||
unsigned int max_speed_hz;
|
||||
unsigned int min_speed_hz;
|
||||
unsigned int fifo_size;
|
||||
bool has_oen;
|
||||
bool has_enhance_clk_div;
|
||||
bool has_pclk;
|
||||
};
|
||||
|
||||
struct meson_spicc_device {
|
||||
struct spi_master *master;
|
||||
struct platform_device *pdev;
|
||||
void __iomem *base;
|
||||
struct clk *core;
|
||||
struct clk *pclk;
|
||||
struct clk *clk;
|
||||
struct spi_message *message;
|
||||
struct spi_transfer *xfer;
|
||||
const struct meson_spicc_data *data;
|
||||
u8 *tx_buf;
|
||||
u8 *rx_buf;
|
||||
unsigned int bytes_per_word;
|
||||
unsigned long tx_remain;
|
||||
unsigned long txb_remain;
|
||||
unsigned long rx_remain;
|
||||
unsigned long rxb_remain;
|
||||
unsigned long xfer_remain;
|
||||
bool is_burst_end;
|
||||
bool is_last_burst;
|
||||
};
|
||||
|
||||
static void meson_spicc_oen_enable(struct meson_spicc_device *spicc)
|
||||
{
|
||||
u32 conf;
|
||||
|
||||
if (!spicc->data->has_oen)
|
||||
return;
|
||||
|
||||
conf = readl_relaxed(spicc->base + SPICC_ENH_CTL0) |
|
||||
SPICC_ENH_MOSI_OEN | SPICC_ENH_CLK_OEN | SPICC_ENH_CS_OEN;
|
||||
|
||||
writel_relaxed(conf, spicc->base + SPICC_ENH_CTL0);
|
||||
}
|
||||
|
||||
static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc)
|
||||
{
|
||||
return !!FIELD_GET(SPICC_TF,
|
||||
|
@ -146,7 +189,7 @@ static inline bool meson_spicc_txfull(struct meson_spicc_device *spicc)
|
|||
|
||||
static inline bool meson_spicc_rxready(struct meson_spicc_device *spicc)
|
||||
{
|
||||
return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF_EN,
|
||||
return FIELD_GET(SPICC_RH | SPICC_RR | SPICC_RF,
|
||||
readl_relaxed(spicc->base + SPICC_STATREG));
|
||||
}
|
||||
|
||||
|
@ -201,34 +244,22 @@ static inline void meson_spicc_tx(struct meson_spicc_device *spicc)
|
|||
spicc->base + SPICC_TXDATA);
|
||||
}
|
||||
|
||||
static inline u32 meson_spicc_setup_rx_irq(struct meson_spicc_device *spicc,
|
||||
u32 irq_ctrl)
|
||||
static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc)
|
||||
{
|
||||
if (spicc->rx_remain > SPICC_FIFO_HALF)
|
||||
irq_ctrl |= SPICC_RH_EN;
|
||||
else
|
||||
irq_ctrl |= SPICC_RR_EN;
|
||||
|
||||
return irq_ctrl;
|
||||
}
|
||||
|
||||
static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc,
|
||||
unsigned int burst_len)
|
||||
{
|
||||
unsigned int burst_len = min_t(unsigned int,
|
||||
spicc->xfer_remain /
|
||||
spicc->bytes_per_word,
|
||||
spicc->data->fifo_size);
|
||||
/* Setup Xfer variables */
|
||||
spicc->tx_remain = burst_len;
|
||||
spicc->rx_remain = burst_len;
|
||||
spicc->xfer_remain -= burst_len * spicc->bytes_per_word;
|
||||
spicc->is_burst_end = false;
|
||||
if (burst_len < SPICC_BURST_MAX || !spicc->xfer_remain)
|
||||
spicc->is_last_burst = true;
|
||||
else
|
||||
spicc->is_last_burst = false;
|
||||
|
||||
/* Setup burst length */
|
||||
writel_bits_relaxed(SPICC_BURSTLENGTH_MASK,
|
||||
FIELD_PREP(SPICC_BURSTLENGTH_MASK,
|
||||
burst_len),
|
||||
burst_len - 1),
|
||||
spicc->base + SPICC_CONREG);
|
||||
|
||||
/* Fill TX FIFO */
|
||||
|
@ -238,36 +269,13 @@ static inline void meson_spicc_setup_burst(struct meson_spicc_device *spicc,
|
|||
static irqreturn_t meson_spicc_irq(int irq, void *data)
|
||||
{
|
||||
struct meson_spicc_device *spicc = (void *) data;
|
||||
u32 ctrl = readl_relaxed(spicc->base + SPICC_INTREG);
|
||||
u32 stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl;
|
||||
|
||||
ctrl &= ~(SPICC_RH_EN | SPICC_RR_EN);
|
||||
writel_bits_relaxed(SPICC_TC, SPICC_TC, spicc->base + SPICC_STATREG);
|
||||
|
||||
/* Empty RX FIFO */
|
||||
meson_spicc_rx(spicc);
|
||||
|
||||
/* Enable TC interrupt since we transferred everything */
|
||||
if (!spicc->tx_remain && !spicc->rx_remain) {
|
||||
spicc->is_burst_end = true;
|
||||
|
||||
/* Enable TC interrupt */
|
||||
ctrl |= SPICC_TC_EN;
|
||||
|
||||
/* Reload IRQ status */
|
||||
stat = readl_relaxed(spicc->base + SPICC_STATREG) & ctrl;
|
||||
}
|
||||
|
||||
/* Check transfer complete */
|
||||
if ((stat & SPICC_TC) && spicc->is_burst_end) {
|
||||
unsigned int burst_len;
|
||||
|
||||
/* Clear TC bit */
|
||||
writel_relaxed(SPICC_TC, spicc->base + SPICC_STATREG);
|
||||
|
||||
/* Disable TC interrupt */
|
||||
ctrl &= ~SPICC_TC_EN;
|
||||
|
||||
if (spicc->is_last_burst) {
|
||||
if (!spicc->xfer_remain) {
|
||||
/* Disable all IRQs */
|
||||
writel(0, spicc->base + SPICC_INTREG);
|
||||
|
||||
|
@ -276,59 +284,56 @@ static irqreturn_t meson_spicc_irq(int irq, void *data)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
burst_len = min_t(unsigned int,
|
||||
spicc->xfer_remain / spicc->bytes_per_word,
|
||||
SPICC_BURST_MAX);
|
||||
|
||||
/* Setup burst */
|
||||
meson_spicc_setup_burst(spicc, burst_len);
|
||||
meson_spicc_setup_burst(spicc);
|
||||
|
||||
/* Restart burst */
|
||||
writel_bits_relaxed(SPICC_XCH, SPICC_XCH,
|
||||
spicc->base + SPICC_CONREG);
|
||||
}
|
||||
|
||||
/* Setup RX interrupt trigger */
|
||||
ctrl = meson_spicc_setup_rx_irq(spicc, ctrl);
|
||||
|
||||
/* Reconfigure interrupts */
|
||||
writel(ctrl, spicc->base + SPICC_INTREG);
|
||||
/* Start burst */
|
||||
writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static u32 meson_spicc_setup_speed(struct meson_spicc_device *spicc, u32 conf,
|
||||
u32 speed)
|
||||
static void meson_spicc_auto_io_delay(struct meson_spicc_device *spicc)
|
||||
{
|
||||
unsigned long parent, value;
|
||||
unsigned int i, div;
|
||||
u32 div, hz;
|
||||
u32 mi_delay, cap_delay;
|
||||
u32 conf;
|
||||
|
||||
parent = clk_get_rate(spicc->core);
|
||||
|
||||
/* Find closest inferior/equal possible speed */
|
||||
for (i = 0 ; i < 7 ; ++i) {
|
||||
/* 2^(data_rate+2) */
|
||||
value = parent >> (i + 2);
|
||||
|
||||
if (value <= speed)
|
||||
break;
|
||||
if (spicc->data->has_enhance_clk_div) {
|
||||
div = FIELD_GET(SPICC_ENH_DATARATE_MASK,
|
||||
readl_relaxed(spicc->base + SPICC_ENH_CTL0));
|
||||
div++;
|
||||
div <<= 1;
|
||||
} else {
|
||||
div = FIELD_GET(SPICC_DATARATE_MASK,
|
||||
readl_relaxed(spicc->base + SPICC_CONREG));
|
||||
div += 2;
|
||||
div = 1 << div;
|
||||
}
|
||||
|
||||
/* If provided speed it lower than max divider, use max divider */
|
||||
if (i > 7) {
|
||||
div = 7;
|
||||
dev_warn_once(&spicc->pdev->dev, "unable to get close to speed %u\n",
|
||||
speed);
|
||||
} else
|
||||
div = i;
|
||||
mi_delay = SPICC_MI_NO_DELAY;
|
||||
cap_delay = SPICC_CAP_AHEAD_2_CYCLE;
|
||||
hz = clk_get_rate(spicc->clk);
|
||||
|
||||
dev_dbg(&spicc->pdev->dev, "parent %lu, speed %u -> %lu (%u)\n",
|
||||
parent, speed, value, div);
|
||||
if (hz >= 100000000)
|
||||
cap_delay = SPICC_CAP_DELAY_1_CYCLE;
|
||||
else if (hz >= 80000000)
|
||||
cap_delay = SPICC_CAP_NO_DELAY;
|
||||
else if (hz >= 40000000)
|
||||
cap_delay = SPICC_CAP_AHEAD_1_CYCLE;
|
||||
else if (div >= 16)
|
||||
mi_delay = SPICC_MI_DELAY_3_CYCLE;
|
||||
else if (div >= 8)
|
||||
mi_delay = SPICC_MI_DELAY_2_CYCLE;
|
||||
else if (div >= 6)
|
||||
mi_delay = SPICC_MI_DELAY_1_CYCLE;
|
||||
|
||||
conf &= ~SPICC_DATARATE_MASK;
|
||||
conf |= FIELD_PREP(SPICC_DATARATE_MASK, div);
|
||||
|
||||
return conf;
|
||||
conf = readl_relaxed(spicc->base + SPICC_TESTREG);
|
||||
conf &= ~(SPICC_MO_DELAY_MASK | SPICC_MI_DELAY_MASK
|
||||
| SPICC_MI_CAP_DELAY_MASK);
|
||||
conf |= FIELD_PREP(SPICC_MI_DELAY_MASK, mi_delay);
|
||||
conf |= FIELD_PREP(SPICC_MI_CAP_DELAY_MASK, cap_delay);
|
||||
writel_relaxed(conf, spicc->base + SPICC_TESTREG);
|
||||
}
|
||||
|
||||
static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
|
||||
|
@ -339,9 +344,6 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
|
|||
/* Read original configuration */
|
||||
conf = conf_orig = readl_relaxed(spicc->base + SPICC_CONREG);
|
||||
|
||||
/* Select closest divider */
|
||||
conf = meson_spicc_setup_speed(spicc, conf, xfer->speed_hz);
|
||||
|
||||
/* Setup word width */
|
||||
conf &= ~SPICC_BITLENGTH_MASK;
|
||||
conf |= FIELD_PREP(SPICC_BITLENGTH_MASK,
|
||||
|
@ -350,6 +352,32 @@ static void meson_spicc_setup_xfer(struct meson_spicc_device *spicc,
|
|||
/* Ignore if unchanged */
|
||||
if (conf != conf_orig)
|
||||
writel_relaxed(conf, spicc->base + SPICC_CONREG);
|
||||
|
||||
clk_set_rate(spicc->clk, xfer->speed_hz);
|
||||
|
||||
meson_spicc_auto_io_delay(spicc);
|
||||
|
||||
writel_relaxed(0, spicc->base + SPICC_DMAREG);
|
||||
}
|
||||
|
||||
static void meson_spicc_reset_fifo(struct meson_spicc_device *spicc)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
if (spicc->data->has_oen)
|
||||
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO,
|
||||
SPICC_ENH_MAIN_CLK_AO,
|
||||
spicc->base + SPICC_ENH_CTL0);
|
||||
|
||||
writel_bits_relaxed(SPICC_FIFORST_W1_MASK, SPICC_FIFORST_W1_MASK,
|
||||
spicc->base + SPICC_TESTREG);
|
||||
|
||||
while (meson_spicc_rxready(spicc))
|
||||
data = readl_relaxed(spicc->base + SPICC_RXDATA);
|
||||
|
||||
if (spicc->data->has_oen)
|
||||
writel_bits_relaxed(SPICC_ENH_MAIN_CLK_AO, 0,
|
||||
spicc->base + SPICC_ENH_CTL0);
|
||||
}
|
||||
|
||||
static int meson_spicc_transfer_one(struct spi_master *master,
|
||||
|
@ -357,8 +385,6 @@ static int meson_spicc_transfer_one(struct spi_master *master,
|
|||
struct spi_transfer *xfer)
|
||||
{
|
||||
struct meson_spicc_device *spicc = spi_master_get_devdata(master);
|
||||
unsigned int burst_len;
|
||||
u32 irq = 0;
|
||||
|
||||
/* Store current transfer */
|
||||
spicc->xfer = xfer;
|
||||
|
@ -372,22 +398,22 @@ static int meson_spicc_transfer_one(struct spi_master *master,
|
|||
spicc->bytes_per_word =
|
||||
DIV_ROUND_UP(spicc->xfer->bits_per_word, 8);
|
||||
|
||||
if (xfer->len % spicc->bytes_per_word)
|
||||
return -EINVAL;
|
||||
|
||||
/* Setup transfer parameters */
|
||||
meson_spicc_setup_xfer(spicc, xfer);
|
||||
|
||||
burst_len = min_t(unsigned int,
|
||||
spicc->xfer_remain / spicc->bytes_per_word,
|
||||
SPICC_BURST_MAX);
|
||||
meson_spicc_reset_fifo(spicc);
|
||||
|
||||
meson_spicc_setup_burst(spicc, burst_len);
|
||||
|
||||
irq = meson_spicc_setup_rx_irq(spicc, irq);
|
||||
/* Setup burst */
|
||||
meson_spicc_setup_burst(spicc);
|
||||
|
||||
/* Start burst */
|
||||
writel_bits_relaxed(SPICC_XCH, SPICC_XCH, spicc->base + SPICC_CONREG);
|
||||
|
||||
/* Enable interrupts */
|
||||
writel_relaxed(irq, spicc->base + SPICC_INTREG);
|
||||
writel_relaxed(SPICC_TC_EN, spicc->base + SPICC_INTREG);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -444,7 +470,7 @@ static int meson_spicc_prepare_message(struct spi_master *master,
|
|||
/* Setup no wait cycles by default */
|
||||
writel_relaxed(0, spicc->base + SPICC_PERIODREG);
|
||||
|
||||
writel_bits_relaxed(BIT(24), BIT(24), spicc->base + SPICC_TESTREG);
|
||||
writel_bits_relaxed(SPICC_LBC_W1, 0, spicc->base + SPICC_TESTREG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -456,9 +482,6 @@ static int meson_spicc_unprepare_transfer(struct spi_master *master)
|
|||
/* Disable all IRQs */
|
||||
writel(0, spicc->base + SPICC_INTREG);
|
||||
|
||||
/* Disable controller */
|
||||
writel_bits_relaxed(SPICC_ENABLE, 0, spicc->base + SPICC_CONREG);
|
||||
|
||||
device_reset_optional(&spicc->pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
@ -477,11 +500,167 @@ static void meson_spicc_cleanup(struct spi_device *spi)
|
|||
spi->controller_state = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The Clock Mux
|
||||
* x-----------------x x------------x x------\
|
||||
* |---| pow2 fixed div |---| pow2 div |----| |
|
||||
* | x-----------------x x------------x | |
|
||||
* src ---| | mux |-- out
|
||||
* | x-----------------x x------------x | |
|
||||
* |---| enh fixed div |---| enh div |0---| |
|
||||
* x-----------------x x------------x x------/
|
||||
*
|
||||
* Clk path for GX series:
|
||||
* src -> pow2 fixed div -> pow2 div -> out
|
||||
*
|
||||
* Clk path for AXG series:
|
||||
* src -> pow2 fixed div -> pow2 div -> mux -> out
|
||||
* src -> enh fixed div -> enh div -> mux -> out
|
||||
*
|
||||
* Clk path for G12A series:
|
||||
* pclk -> pow2 fixed div -> pow2 div -> mux -> out
|
||||
* pclk -> enh fixed div -> enh div -> mux -> out
|
||||
*/
|
||||
|
||||
static int meson_spicc_clk_init(struct meson_spicc_device *spicc)
|
||||
{
|
||||
struct device *dev = &spicc->pdev->dev;
|
||||
struct clk_fixed_factor *pow2_fixed_div, *enh_fixed_div;
|
||||
struct clk_divider *pow2_div, *enh_div;
|
||||
struct clk_mux *mux;
|
||||
struct clk_init_data init;
|
||||
struct clk *clk;
|
||||
struct clk_parent_data parent_data[2];
|
||||
char name[64];
|
||||
|
||||
memset(&init, 0, sizeof(init));
|
||||
memset(&parent_data, 0, sizeof(parent_data));
|
||||
|
||||
init.parent_data = parent_data;
|
||||
|
||||
/* algorithm for pow2 div: rate = freq / 4 / (2 ^ N) */
|
||||
|
||||
pow2_fixed_div = devm_kzalloc(dev, sizeof(*pow2_fixed_div), GFP_KERNEL);
|
||||
if (!pow2_fixed_div)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "%s#pow2_fixed_div", dev_name(dev));
|
||||
init.name = name;
|
||||
init.ops = &clk_fixed_factor_ops;
|
||||
init.flags = 0;
|
||||
if (spicc->data->has_pclk)
|
||||
parent_data[0].hw = __clk_get_hw(spicc->pclk);
|
||||
else
|
||||
parent_data[0].hw = __clk_get_hw(spicc->core);
|
||||
init.num_parents = 1;
|
||||
|
||||
pow2_fixed_div->mult = 1,
|
||||
pow2_fixed_div->div = 4,
|
||||
pow2_fixed_div->hw.init = &init;
|
||||
|
||||
clk = devm_clk_register(dev, &pow2_fixed_div->hw);
|
||||
if (WARN_ON(IS_ERR(clk)))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
pow2_div = devm_kzalloc(dev, sizeof(*pow2_div), GFP_KERNEL);
|
||||
if (!pow2_div)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "%s#pow2_div", dev_name(dev));
|
||||
init.name = name;
|
||||
init.ops = &clk_divider_ops;
|
||||
init.flags = CLK_SET_RATE_PARENT;
|
||||
parent_data[0].hw = &pow2_fixed_div->hw;
|
||||
init.num_parents = 1;
|
||||
|
||||
pow2_div->shift = 16,
|
||||
pow2_div->width = 3,
|
||||
pow2_div->flags = CLK_DIVIDER_POWER_OF_TWO,
|
||||
pow2_div->reg = spicc->base + SPICC_CONREG;
|
||||
pow2_div->hw.init = &init;
|
||||
|
||||
clk = devm_clk_register(dev, &pow2_div->hw);
|
||||
if (WARN_ON(IS_ERR(clk)))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
if (!spicc->data->has_enhance_clk_div) {
|
||||
spicc->clk = clk;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* algorithm for enh div: rate = freq / 2 / (N + 1) */
|
||||
|
||||
enh_fixed_div = devm_kzalloc(dev, sizeof(*enh_fixed_div), GFP_KERNEL);
|
||||
if (!enh_fixed_div)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "%s#enh_fixed_div", dev_name(dev));
|
||||
init.name = name;
|
||||
init.ops = &clk_fixed_factor_ops;
|
||||
init.flags = 0;
|
||||
if (spicc->data->has_pclk)
|
||||
parent_data[0].hw = __clk_get_hw(spicc->pclk);
|
||||
else
|
||||
parent_data[0].hw = __clk_get_hw(spicc->core);
|
||||
init.num_parents = 1;
|
||||
|
||||
enh_fixed_div->mult = 1,
|
||||
enh_fixed_div->div = 2,
|
||||
enh_fixed_div->hw.init = &init;
|
||||
|
||||
clk = devm_clk_register(dev, &enh_fixed_div->hw);
|
||||
if (WARN_ON(IS_ERR(clk)))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
enh_div = devm_kzalloc(dev, sizeof(*enh_div), GFP_KERNEL);
|
||||
if (!enh_div)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "%s#enh_div", dev_name(dev));
|
||||
init.name = name;
|
||||
init.ops = &clk_divider_ops;
|
||||
init.flags = CLK_SET_RATE_PARENT;
|
||||
parent_data[0].hw = &enh_fixed_div->hw;
|
||||
init.num_parents = 1;
|
||||
|
||||
enh_div->shift = 16,
|
||||
enh_div->width = 8,
|
||||
enh_div->reg = spicc->base + SPICC_ENH_CTL0;
|
||||
enh_div->hw.init = &init;
|
||||
|
||||
clk = devm_clk_register(dev, &enh_div->hw);
|
||||
if (WARN_ON(IS_ERR(clk)))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
if (!mux)
|
||||
return -ENOMEM;
|
||||
|
||||
snprintf(name, sizeof(name), "%s#sel", dev_name(dev));
|
||||
init.name = name;
|
||||
init.ops = &clk_mux_ops;
|
||||
parent_data[0].hw = &pow2_div->hw;
|
||||
parent_data[1].hw = &enh_div->hw;
|
||||
init.num_parents = 2;
|
||||
init.flags = CLK_SET_RATE_PARENT;
|
||||
|
||||
mux->mask = 0x1,
|
||||
mux->shift = 24,
|
||||
mux->reg = spicc->base + SPICC_ENH_CTL0;
|
||||
mux->hw.init = &init;
|
||||
|
||||
spicc->clk = devm_clk_register(dev, &mux->hw);
|
||||
if (WARN_ON(IS_ERR(spicc->clk)))
|
||||
return PTR_ERR(spicc->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_spicc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_master *master;
|
||||
struct meson_spicc_device *spicc;
|
||||
int ret, irq, rate;
|
||||
int ret, irq;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof(*spicc));
|
||||
if (!master) {
|
||||
|
@ -491,6 +670,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
spicc = spi_master_get_devdata(master);
|
||||
spicc->master = master;
|
||||
|
||||
spicc->data = of_device_get_match_data(&pdev->dev);
|
||||
if (!spicc->data) {
|
||||
dev_err(&pdev->dev, "failed to get match data\n");
|
||||
ret = -EINVAL;
|
||||
goto out_master;
|
||||
}
|
||||
|
||||
spicc->pdev = pdev;
|
||||
platform_set_drvdata(pdev, spicc);
|
||||
|
||||
|
@ -501,6 +687,10 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
goto out_master;
|
||||
}
|
||||
|
||||
/* Set master mode and enable controller */
|
||||
writel_relaxed(SPICC_ENABLE | SPICC_MODE_MASTER,
|
||||
spicc->base + SPICC_CONREG);
|
||||
|
||||
/* Disable all IRQs */
|
||||
writel_relaxed(0, spicc->base + SPICC_INTREG);
|
||||
|
||||
|
@ -519,12 +709,26 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
goto out_master;
|
||||
}
|
||||
|
||||
if (spicc->data->has_pclk) {
|
||||
spicc->pclk = devm_clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(spicc->pclk)) {
|
||||
dev_err(&pdev->dev, "pclk clock request failed\n");
|
||||
ret = PTR_ERR(spicc->pclk);
|
||||
goto out_master;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(spicc->core);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "core clock enable failed\n");
|
||||
goto out_master;
|
||||
}
|
||||
rate = clk_get_rate(spicc->core);
|
||||
|
||||
ret = clk_prepare_enable(spicc->pclk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "pclk clock enable failed\n");
|
||||
goto out_master;
|
||||
}
|
||||
|
||||
device_reset_optional(&pdev->dev);
|
||||
|
||||
|
@ -536,7 +740,8 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
SPI_BPW_MASK(16) |
|
||||
SPI_BPW_MASK(8);
|
||||
master->flags = (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX);
|
||||
master->min_speed_hz = rate >> 9;
|
||||
master->min_speed_hz = spicc->data->min_speed_hz;
|
||||
master->max_speed_hz = spicc->data->max_speed_hz;
|
||||
master->setup = meson_spicc_setup;
|
||||
master->cleanup = meson_spicc_cleanup;
|
||||
master->prepare_message = meson_spicc_prepare_message;
|
||||
|
@ -544,11 +749,13 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
master->transfer_one = meson_spicc_transfer_one;
|
||||
master->use_gpio_descriptors = true;
|
||||
|
||||
/* Setup max rate according to the Meson GX datasheet */
|
||||
if ((rate >> 2) > SPICC_MAX_FREQ)
|
||||
master->max_speed_hz = SPICC_MAX_FREQ;
|
||||
else
|
||||
master->max_speed_hz = rate >> 2;
|
||||
meson_spicc_oen_enable(spicc);
|
||||
|
||||
ret = meson_spicc_clk_init(spicc);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "clock registration failed\n");
|
||||
goto out_master;
|
||||
}
|
||||
|
||||
ret = devm_spi_register_master(&pdev->dev, master);
|
||||
if (ret) {
|
||||
|
@ -560,6 +767,7 @@ static int meson_spicc_probe(struct platform_device *pdev)
|
|||
|
||||
out_clk:
|
||||
clk_disable_unprepare(spicc->core);
|
||||
clk_disable_unprepare(spicc->pclk);
|
||||
|
||||
out_master:
|
||||
spi_master_put(master);
|
||||
|
@ -575,13 +783,47 @@ static int meson_spicc_remove(struct platform_device *pdev)
|
|||
writel(0, spicc->base + SPICC_CONREG);
|
||||
|
||||
clk_disable_unprepare(spicc->core);
|
||||
clk_disable_unprepare(spicc->pclk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct meson_spicc_data meson_spicc_gx_data = {
|
||||
.max_speed_hz = 30000000,
|
||||
.min_speed_hz = 325000,
|
||||
.fifo_size = 16,
|
||||
};
|
||||
|
||||
static const struct meson_spicc_data meson_spicc_axg_data = {
|
||||
.max_speed_hz = 80000000,
|
||||
.min_speed_hz = 325000,
|
||||
.fifo_size = 16,
|
||||
.has_oen = true,
|
||||
.has_enhance_clk_div = true,
|
||||
};
|
||||
|
||||
static const struct meson_spicc_data meson_spicc_g12a_data = {
|
||||
.max_speed_hz = 166666666,
|
||||
.min_speed_hz = 50000,
|
||||
.fifo_size = 15,
|
||||
.has_oen = true,
|
||||
.has_enhance_clk_div = true,
|
||||
.has_pclk = true,
|
||||
};
|
||||
|
||||
static const struct of_device_id meson_spicc_of_match[] = {
|
||||
{ .compatible = "amlogic,meson-gx-spicc", },
|
||||
{ .compatible = "amlogic,meson-axg-spicc", },
|
||||
{
|
||||
.compatible = "amlogic,meson-gx-spicc",
|
||||
.data = &meson_spicc_gx_data,
|
||||
},
|
||||
{
|
||||
.compatible = "amlogic,meson-axg-spicc",
|
||||
.data = &meson_spicc_axg_data,
|
||||
},
|
||||
{
|
||||
.compatible = "amlogic,meson-g12a-spicc",
|
||||
.data = &meson_spicc_g12a_data,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, meson_spicc_of_match);
|
||||
|
|
|
@ -0,0 +1,689 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Mediatek SPI NOR controller driver
|
||||
//
|
||||
// Copyright (C) 2020 Chuanhong Guo <gch981213@gmail.com>
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#define DRIVER_NAME "mtk-spi-nor"
|
||||
|
||||
#define MTK_NOR_REG_CMD 0x00
|
||||
#define MTK_NOR_CMD_WRITE BIT(4)
|
||||
#define MTK_NOR_CMD_PROGRAM BIT(2)
|
||||
#define MTK_NOR_CMD_READ BIT(0)
|
||||
#define MTK_NOR_CMD_MASK GENMASK(5, 0)
|
||||
|
||||
#define MTK_NOR_REG_PRG_CNT 0x04
|
||||
#define MTK_NOR_REG_RDATA 0x0c
|
||||
|
||||
#define MTK_NOR_REG_RADR0 0x10
|
||||
#define MTK_NOR_REG_RADR(n) (MTK_NOR_REG_RADR0 + 4 * (n))
|
||||
#define MTK_NOR_REG_RADR3 0xc8
|
||||
|
||||
#define MTK_NOR_REG_WDATA 0x1c
|
||||
|
||||
#define MTK_NOR_REG_PRGDATA0 0x20
|
||||
#define MTK_NOR_REG_PRGDATA(n) (MTK_NOR_REG_PRGDATA0 + 4 * (n))
|
||||
#define MTK_NOR_REG_PRGDATA_MAX 5
|
||||
|
||||
#define MTK_NOR_REG_SHIFT0 0x38
|
||||
#define MTK_NOR_REG_SHIFT(n) (MTK_NOR_REG_SHIFT0 + 4 * (n))
|
||||
#define MTK_NOR_REG_SHIFT_MAX 9
|
||||
|
||||
#define MTK_NOR_REG_CFG1 0x60
|
||||
#define MTK_NOR_FAST_READ BIT(0)
|
||||
|
||||
#define MTK_NOR_REG_CFG2 0x64
|
||||
#define MTK_NOR_WR_CUSTOM_OP_EN BIT(4)
|
||||
#define MTK_NOR_WR_BUF_EN BIT(0)
|
||||
|
||||
#define MTK_NOR_REG_PP_DATA 0x98
|
||||
|
||||
#define MTK_NOR_REG_IRQ_STAT 0xa8
|
||||
#define MTK_NOR_REG_IRQ_EN 0xac
|
||||
#define MTK_NOR_IRQ_DMA BIT(7)
|
||||
#define MTK_NOR_IRQ_MASK GENMASK(7, 0)
|
||||
|
||||
#define MTK_NOR_REG_CFG3 0xb4
|
||||
#define MTK_NOR_DISABLE_WREN BIT(7)
|
||||
#define MTK_NOR_DISABLE_SR_POLL BIT(5)
|
||||
|
||||
#define MTK_NOR_REG_WP 0xc4
|
||||
#define MTK_NOR_ENABLE_SF_CMD 0x30
|
||||
|
||||
#define MTK_NOR_REG_BUSCFG 0xcc
|
||||
#define MTK_NOR_4B_ADDR BIT(4)
|
||||
#define MTK_NOR_QUAD_ADDR BIT(3)
|
||||
#define MTK_NOR_QUAD_READ BIT(2)
|
||||
#define MTK_NOR_DUAL_ADDR BIT(1)
|
||||
#define MTK_NOR_DUAL_READ BIT(0)
|
||||
#define MTK_NOR_BUS_MODE_MASK GENMASK(4, 0)
|
||||
|
||||
#define MTK_NOR_REG_DMA_CTL 0x718
|
||||
#define MTK_NOR_DMA_START BIT(0)
|
||||
|
||||
#define MTK_NOR_REG_DMA_FADR 0x71c
|
||||
#define MTK_NOR_REG_DMA_DADR 0x720
|
||||
#define MTK_NOR_REG_DMA_END_DADR 0x724
|
||||
|
||||
#define MTK_NOR_PRG_MAX_SIZE 6
|
||||
// Reading DMA src/dst addresses have to be 16-byte aligned
|
||||
#define MTK_NOR_DMA_ALIGN 16
|
||||
#define MTK_NOR_DMA_ALIGN_MASK (MTK_NOR_DMA_ALIGN - 1)
|
||||
// and we allocate a bounce buffer if destination address isn't aligned.
|
||||
#define MTK_NOR_BOUNCE_BUF_SIZE PAGE_SIZE
|
||||
|
||||
// Buffered page program can do one 128-byte transfer
|
||||
#define MTK_NOR_PP_SIZE 128
|
||||
|
||||
#define CLK_TO_US(sp, clkcnt) ((clkcnt) * 1000000 / sp->spi_freq)
|
||||
|
||||
struct mtk_nor {
|
||||
struct spi_controller *ctlr;
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
u8 *buffer;
|
||||
struct clk *spi_clk;
|
||||
struct clk *ctlr_clk;
|
||||
unsigned int spi_freq;
|
||||
bool wbuf_en;
|
||||
bool has_irq;
|
||||
struct completion op_done;
|
||||
};
|
||||
|
||||
static inline void mtk_nor_rmw(struct mtk_nor *sp, u32 reg, u32 set, u32 clr)
|
||||
{
|
||||
u32 val = readl(sp->base + reg);
|
||||
|
||||
val &= ~clr;
|
||||
val |= set;
|
||||
writel(val, sp->base + reg);
|
||||
}
|
||||
|
||||
static inline int mtk_nor_cmd_exec(struct mtk_nor *sp, u32 cmd, ulong clk)
|
||||
{
|
||||
ulong delay = CLK_TO_US(sp, clk);
|
||||
u32 reg;
|
||||
int ret;
|
||||
|
||||
writel(cmd, sp->base + MTK_NOR_REG_CMD);
|
||||
ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CMD, reg, !(reg & cmd),
|
||||
delay / 3, (delay + 1) * 200);
|
||||
if (ret < 0)
|
||||
dev_err(sp->dev, "command %u timeout.\n", cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtk_nor_set_addr(struct mtk_nor *sp, const struct spi_mem_op *op)
|
||||
{
|
||||
u32 addr = op->addr.val;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
writeb(addr & 0xff, sp->base + MTK_NOR_REG_RADR(i));
|
||||
addr >>= 8;
|
||||
}
|
||||
if (op->addr.nbytes == 4) {
|
||||
writeb(addr & 0xff, sp->base + MTK_NOR_REG_RADR3);
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, MTK_NOR_4B_ADDR, 0);
|
||||
} else {
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, 0, MTK_NOR_4B_ADDR);
|
||||
}
|
||||
}
|
||||
|
||||
static bool mtk_nor_match_read(const struct spi_mem_op *op)
|
||||
{
|
||||
int dummy = 0;
|
||||
|
||||
if (op->dummy.buswidth)
|
||||
dummy = op->dummy.nbytes * BITS_PER_BYTE / op->dummy.buswidth;
|
||||
|
||||
if ((op->data.buswidth == 2) || (op->data.buswidth == 4)) {
|
||||
if (op->addr.buswidth == 1)
|
||||
return dummy == 8;
|
||||
else if (op->addr.buswidth == 2)
|
||||
return dummy == 4;
|
||||
else if (op->addr.buswidth == 4)
|
||||
return dummy == 6;
|
||||
} else if ((op->addr.buswidth == 1) && (op->data.buswidth == 1)) {
|
||||
if (op->cmd.opcode == 0x03)
|
||||
return dummy == 0;
|
||||
else if (op->cmd.opcode == 0x0b)
|
||||
return dummy == 8;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int mtk_nor_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
if (!op->data.nbytes)
|
||||
return 0;
|
||||
|
||||
if ((op->addr.nbytes == 3) || (op->addr.nbytes == 4)) {
|
||||
if ((op->data.dir == SPI_MEM_DATA_IN) &&
|
||||
mtk_nor_match_read(op)) {
|
||||
if ((op->addr.val & MTK_NOR_DMA_ALIGN_MASK) ||
|
||||
(op->data.nbytes < MTK_NOR_DMA_ALIGN))
|
||||
op->data.nbytes = 1;
|
||||
else if (!((ulong)(op->data.buf.in) &
|
||||
MTK_NOR_DMA_ALIGN_MASK))
|
||||
op->data.nbytes &= ~MTK_NOR_DMA_ALIGN_MASK;
|
||||
else if (op->data.nbytes > MTK_NOR_BOUNCE_BUF_SIZE)
|
||||
op->data.nbytes = MTK_NOR_BOUNCE_BUF_SIZE;
|
||||
return 0;
|
||||
} else if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
if (op->data.nbytes >= MTK_NOR_PP_SIZE)
|
||||
op->data.nbytes = MTK_NOR_PP_SIZE;
|
||||
else
|
||||
op->data.nbytes = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
len = MTK_NOR_PRG_MAX_SIZE - sizeof(op->cmd.opcode) - op->addr.nbytes -
|
||||
op->dummy.nbytes;
|
||||
if (op->data.nbytes > len)
|
||||
op->data.nbytes = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool mtk_nor_supports_op(struct spi_mem *mem,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
if (op->cmd.buswidth != 1)
|
||||
return false;
|
||||
|
||||
if ((op->addr.nbytes == 3) || (op->addr.nbytes == 4)) {
|
||||
if ((op->data.dir == SPI_MEM_DATA_IN) && mtk_nor_match_read(op))
|
||||
return true;
|
||||
else if (op->data.dir == SPI_MEM_DATA_OUT)
|
||||
return (op->addr.buswidth == 1) &&
|
||||
(op->dummy.buswidth == 0) &&
|
||||
(op->data.buswidth == 1);
|
||||
}
|
||||
len = sizeof(op->cmd.opcode) + op->addr.nbytes + op->dummy.nbytes;
|
||||
if ((len > MTK_NOR_PRG_MAX_SIZE) ||
|
||||
((op->data.nbytes) && (len == MTK_NOR_PRG_MAX_SIZE)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mtk_nor_setup_bus(struct mtk_nor *sp, const struct spi_mem_op *op)
|
||||
{
|
||||
u32 reg = 0;
|
||||
|
||||
if (op->addr.nbytes == 4)
|
||||
reg |= MTK_NOR_4B_ADDR;
|
||||
|
||||
if (op->data.buswidth == 4) {
|
||||
reg |= MTK_NOR_QUAD_READ;
|
||||
writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA(4));
|
||||
if (op->addr.buswidth == 4)
|
||||
reg |= MTK_NOR_QUAD_ADDR;
|
||||
} else if (op->data.buswidth == 2) {
|
||||
reg |= MTK_NOR_DUAL_READ;
|
||||
writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA(3));
|
||||
if (op->addr.buswidth == 2)
|
||||
reg |= MTK_NOR_DUAL_ADDR;
|
||||
} else {
|
||||
if (op->cmd.opcode == 0x0b)
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_CFG1, MTK_NOR_FAST_READ, 0);
|
||||
else
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_CFG1, 0, MTK_NOR_FAST_READ);
|
||||
}
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_BUSCFG, reg, MTK_NOR_BUS_MODE_MASK);
|
||||
}
|
||||
|
||||
static int mtk_nor_read_dma(struct mtk_nor *sp, u32 from, unsigned int length,
|
||||
u8 *buffer)
|
||||
{
|
||||
int ret = 0;
|
||||
ulong delay;
|
||||
u32 reg;
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
dma_addr = dma_map_single(sp->dev, buffer, length, DMA_FROM_DEVICE);
|
||||
if (dma_mapping_error(sp->dev, dma_addr)) {
|
||||
dev_err(sp->dev, "failed to map dma buffer.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(from, sp->base + MTK_NOR_REG_DMA_FADR);
|
||||
writel(dma_addr, sp->base + MTK_NOR_REG_DMA_DADR);
|
||||
writel(dma_addr + length, sp->base + MTK_NOR_REG_DMA_END_DADR);
|
||||
|
||||
if (sp->has_irq) {
|
||||
reinit_completion(&sp->op_done);
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_IRQ_EN, MTK_NOR_IRQ_DMA, 0);
|
||||
}
|
||||
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_DMA_CTL, MTK_NOR_DMA_START, 0);
|
||||
|
||||
delay = CLK_TO_US(sp, (length + 5) * BITS_PER_BYTE);
|
||||
|
||||
if (sp->has_irq) {
|
||||
if (!wait_for_completion_timeout(&sp->op_done,
|
||||
(delay + 1) * 100))
|
||||
ret = -ETIMEDOUT;
|
||||
} else {
|
||||
ret = readl_poll_timeout(sp->base + MTK_NOR_REG_DMA_CTL, reg,
|
||||
!(reg & MTK_NOR_DMA_START), delay / 3,
|
||||
(delay + 1) * 100);
|
||||
}
|
||||
|
||||
dma_unmap_single(sp->dev, dma_addr, length, DMA_FROM_DEVICE);
|
||||
if (ret < 0)
|
||||
dev_err(sp->dev, "dma read timeout.\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_read_bounce(struct mtk_nor *sp, u32 from,
|
||||
unsigned int length, u8 *buffer)
|
||||
{
|
||||
unsigned int rdlen;
|
||||
int ret;
|
||||
|
||||
if (length & MTK_NOR_DMA_ALIGN_MASK)
|
||||
rdlen = (length + MTK_NOR_DMA_ALIGN) & ~MTK_NOR_DMA_ALIGN_MASK;
|
||||
else
|
||||
rdlen = length;
|
||||
|
||||
ret = mtk_nor_read_dma(sp, from, rdlen, sp->buffer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memcpy(buffer, sp->buffer, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_nor_read_pio(struct mtk_nor *sp, const struct spi_mem_op *op)
|
||||
{
|
||||
u8 *buf = op->data.buf.in;
|
||||
int ret;
|
||||
|
||||
ret = mtk_nor_cmd_exec(sp, MTK_NOR_CMD_READ, 6 * BITS_PER_BYTE);
|
||||
if (!ret)
|
||||
buf[0] = readb(sp->base + MTK_NOR_REG_RDATA);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_write_buffer_enable(struct mtk_nor *sp)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (sp->wbuf_en)
|
||||
return 0;
|
||||
|
||||
val = readl(sp->base + MTK_NOR_REG_CFG2);
|
||||
writel(val | MTK_NOR_WR_BUF_EN, sp->base + MTK_NOR_REG_CFG2);
|
||||
ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CFG2, val,
|
||||
val & MTK_NOR_WR_BUF_EN, 0, 10000);
|
||||
if (!ret)
|
||||
sp->wbuf_en = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_write_buffer_disable(struct mtk_nor *sp)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (!sp->wbuf_en)
|
||||
return 0;
|
||||
val = readl(sp->base + MTK_NOR_REG_CFG2);
|
||||
writel(val & ~MTK_NOR_WR_BUF_EN, sp->base + MTK_NOR_REG_CFG2);
|
||||
ret = readl_poll_timeout(sp->base + MTK_NOR_REG_CFG2, val,
|
||||
!(val & MTK_NOR_WR_BUF_EN), 0, 10000);
|
||||
if (!ret)
|
||||
sp->wbuf_en = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_nor_pp_buffered(struct mtk_nor *sp, const struct spi_mem_op *op)
|
||||
{
|
||||
const u8 *buf = op->data.buf.out;
|
||||
u32 val;
|
||||
int ret, i;
|
||||
|
||||
ret = mtk_nor_write_buffer_enable(sp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < op->data.nbytes; i += 4) {
|
||||
val = buf[i + 3] << 24 | buf[i + 2] << 16 | buf[i + 1] << 8 |
|
||||
buf[i];
|
||||
writel(val, sp->base + MTK_NOR_REG_PP_DATA);
|
||||
}
|
||||
return mtk_nor_cmd_exec(sp, MTK_NOR_CMD_WRITE,
|
||||
(op->data.nbytes + 5) * BITS_PER_BYTE);
|
||||
}
|
||||
|
||||
static int mtk_nor_pp_unbuffered(struct mtk_nor *sp,
|
||||
const struct spi_mem_op *op)
|
||||
{
|
||||
const u8 *buf = op->data.buf.out;
|
||||
int ret;
|
||||
|
||||
ret = mtk_nor_write_buffer_disable(sp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
writeb(buf[0], sp->base + MTK_NOR_REG_WDATA);
|
||||
return mtk_nor_cmd_exec(sp, MTK_NOR_CMD_WRITE, 6 * BITS_PER_BYTE);
|
||||
}
|
||||
|
||||
int mtk_nor_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
||||
{
|
||||
struct mtk_nor *sp = spi_controller_get_devdata(mem->spi->master);
|
||||
int ret;
|
||||
|
||||
if ((op->data.nbytes == 0) ||
|
||||
((op->addr.nbytes != 3) && (op->addr.nbytes != 4)))
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (op->data.dir == SPI_MEM_DATA_OUT) {
|
||||
mtk_nor_set_addr(sp, op);
|
||||
writeb(op->cmd.opcode, sp->base + MTK_NOR_REG_PRGDATA0);
|
||||
if (op->data.nbytes == MTK_NOR_PP_SIZE)
|
||||
return mtk_nor_pp_buffered(sp, op);
|
||||
return mtk_nor_pp_unbuffered(sp, op);
|
||||
}
|
||||
|
||||
if ((op->data.dir == SPI_MEM_DATA_IN) && mtk_nor_match_read(op)) {
|
||||
ret = mtk_nor_write_buffer_disable(sp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
mtk_nor_setup_bus(sp, op);
|
||||
if (op->data.nbytes == 1) {
|
||||
mtk_nor_set_addr(sp, op);
|
||||
return mtk_nor_read_pio(sp, op);
|
||||
} else if (((ulong)(op->data.buf.in) &
|
||||
MTK_NOR_DMA_ALIGN_MASK)) {
|
||||
return mtk_nor_read_bounce(sp, op->addr.val,
|
||||
op->data.nbytes,
|
||||
op->data.buf.in);
|
||||
} else {
|
||||
return mtk_nor_read_dma(sp, op->addr.val,
|
||||
op->data.nbytes,
|
||||
op->data.buf.in);
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int mtk_nor_setup(struct spi_device *spi)
|
||||
{
|
||||
struct mtk_nor *sp = spi_controller_get_devdata(spi->master);
|
||||
|
||||
if (spi->max_speed_hz && (spi->max_speed_hz < sp->spi_freq)) {
|
||||
dev_err(&spi->dev, "spi clock should be %u Hz.\n",
|
||||
sp->spi_freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
spi->max_speed_hz = sp->spi_freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_nor_transfer_one_message(struct spi_controller *master,
|
||||
struct spi_message *m)
|
||||
{
|
||||
struct mtk_nor *sp = spi_controller_get_devdata(master);
|
||||
struct spi_transfer *t = NULL;
|
||||
unsigned long trx_len = 0;
|
||||
int stat = 0;
|
||||
int reg_offset = MTK_NOR_REG_PRGDATA_MAX;
|
||||
void __iomem *reg;
|
||||
const u8 *txbuf;
|
||||
u8 *rxbuf;
|
||||
int i;
|
||||
|
||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||||
txbuf = t->tx_buf;
|
||||
for (i = 0; i < t->len; i++, reg_offset--) {
|
||||
reg = sp->base + MTK_NOR_REG_PRGDATA(reg_offset);
|
||||
if (txbuf)
|
||||
writeb(txbuf[i], reg);
|
||||
else
|
||||
writeb(0, reg);
|
||||
}
|
||||
trx_len += t->len;
|
||||
}
|
||||
|
||||
writel(trx_len * BITS_PER_BYTE, sp->base + MTK_NOR_REG_PRG_CNT);
|
||||
|
||||
stat = mtk_nor_cmd_exec(sp, MTK_NOR_CMD_PROGRAM,
|
||||
trx_len * BITS_PER_BYTE);
|
||||
if (stat < 0)
|
||||
goto msg_done;
|
||||
|
||||
reg_offset = trx_len - 1;
|
||||
list_for_each_entry(t, &m->transfers, transfer_list) {
|
||||
rxbuf = t->rx_buf;
|
||||
for (i = 0; i < t->len; i++, reg_offset--) {
|
||||
reg = sp->base + MTK_NOR_REG_SHIFT(reg_offset);
|
||||
if (rxbuf)
|
||||
rxbuf[i] = readb(reg);
|
||||
}
|
||||
}
|
||||
|
||||
m->actual_length = trx_len;
|
||||
msg_done:
|
||||
m->status = stat;
|
||||
spi_finalize_current_message(master);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_nor_disable_clk(struct mtk_nor *sp)
|
||||
{
|
||||
clk_disable_unprepare(sp->spi_clk);
|
||||
clk_disable_unprepare(sp->ctlr_clk);
|
||||
}
|
||||
|
||||
static int mtk_nor_enable_clk(struct mtk_nor *sp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(sp->spi_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_prepare_enable(sp->ctlr_clk);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(sp->spi_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_nor_init(struct mtk_nor *sp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mtk_nor_enable_clk(sp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sp->spi_freq = clk_get_rate(sp->spi_clk);
|
||||
|
||||
writel(MTK_NOR_ENABLE_SF_CMD, sp->base + MTK_NOR_REG_WP);
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_CFG2, MTK_NOR_WR_CUSTOM_OP_EN, 0);
|
||||
mtk_nor_rmw(sp, MTK_NOR_REG_CFG3,
|
||||
MTK_NOR_DISABLE_WREN | MTK_NOR_DISABLE_SR_POLL, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t mtk_nor_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct mtk_nor *sp = data;
|
||||
u32 irq_status, irq_enabled;
|
||||
|
||||
irq_status = readl(sp->base + MTK_NOR_REG_IRQ_STAT);
|
||||
irq_enabled = readl(sp->base + MTK_NOR_REG_IRQ_EN);
|
||||
// write status back to clear interrupt
|
||||
writel(irq_status, sp->base + MTK_NOR_REG_IRQ_STAT);
|
||||
|
||||
if (!(irq_status & irq_enabled))
|
||||
return IRQ_NONE;
|
||||
|
||||
if (irq_status & MTK_NOR_IRQ_DMA) {
|
||||
complete(&sp->op_done);
|
||||
writel(0, sp->base + MTK_NOR_REG_IRQ_EN);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static size_t mtk_max_msg_size(struct spi_device *spi)
|
||||
{
|
||||
return MTK_NOR_PRG_MAX_SIZE;
|
||||
}
|
||||
|
||||
static const struct spi_controller_mem_ops mtk_nor_mem_ops = {
|
||||
.adjust_op_size = mtk_nor_adjust_op_size,
|
||||
.supports_op = mtk_nor_supports_op,
|
||||
.exec_op = mtk_nor_exec_op
|
||||
};
|
||||
|
||||
static const struct of_device_id mtk_nor_match[] = {
|
||||
{ .compatible = "mediatek,mt8173-nor" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mtk_nor_match);
|
||||
|
||||
static int mtk_nor_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *ctlr;
|
||||
struct mtk_nor *sp;
|
||||
void __iomem *base;
|
||||
u8 *buffer;
|
||||
struct clk *spi_clk, *ctlr_clk;
|
||||
int ret, irq;
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
spi_clk = devm_clk_get(&pdev->dev, "spi");
|
||||
if (IS_ERR(spi_clk))
|
||||
return PTR_ERR(spi_clk);
|
||||
|
||||
ctlr_clk = devm_clk_get(&pdev->dev, "sf");
|
||||
if (IS_ERR(ctlr_clk))
|
||||
return PTR_ERR(ctlr_clk);
|
||||
|
||||
buffer = devm_kmalloc(&pdev->dev,
|
||||
MTK_NOR_BOUNCE_BUF_SIZE + MTK_NOR_DMA_ALIGN,
|
||||
GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
if ((ulong)buffer & MTK_NOR_DMA_ALIGN_MASK)
|
||||
buffer = (u8 *)(((ulong)buffer + MTK_NOR_DMA_ALIGN) &
|
||||
~MTK_NOR_DMA_ALIGN_MASK);
|
||||
|
||||
ctlr = spi_alloc_master(&pdev->dev, sizeof(*sp));
|
||||
if (!ctlr) {
|
||||
dev_err(&pdev->dev, "failed to allocate spi controller\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
ctlr->dev.of_node = pdev->dev.of_node;
|
||||
ctlr->max_message_size = mtk_max_msg_size;
|
||||
ctlr->mem_ops = &mtk_nor_mem_ops;
|
||||
ctlr->mode_bits = SPI_RX_DUAL | SPI_RX_QUAD | SPI_TX_DUAL | SPI_TX_QUAD;
|
||||
ctlr->num_chipselect = 1;
|
||||
ctlr->setup = mtk_nor_setup;
|
||||
ctlr->transfer_one_message = mtk_nor_transfer_one_message;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, ctlr);
|
||||
|
||||
sp = spi_controller_get_devdata(ctlr);
|
||||
sp->base = base;
|
||||
sp->buffer = buffer;
|
||||
sp->has_irq = false;
|
||||
sp->wbuf_en = false;
|
||||
sp->ctlr = ctlr;
|
||||
sp->dev = &pdev->dev;
|
||||
sp->spi_clk = spi_clk;
|
||||
sp->ctlr_clk = ctlr_clk;
|
||||
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_warn(sp->dev, "IRQ not available.");
|
||||
} else {
|
||||
writel(MTK_NOR_IRQ_MASK, base + MTK_NOR_REG_IRQ_STAT);
|
||||
writel(0, base + MTK_NOR_REG_IRQ_EN);
|
||||
ret = devm_request_irq(sp->dev, irq, mtk_nor_irq_handler, 0,
|
||||
pdev->name, sp);
|
||||
if (ret < 0) {
|
||||
dev_warn(sp->dev, "failed to request IRQ.");
|
||||
} else {
|
||||
init_completion(&sp->op_done);
|
||||
sp->has_irq = true;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mtk_nor_init(sp);
|
||||
if (ret < 0) {
|
||||
kfree(ctlr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "spi frequency: %d Hz\n", sp->spi_freq);
|
||||
|
||||
return devm_spi_register_controller(&pdev->dev, ctlr);
|
||||
}
|
||||
|
||||
static int mtk_nor_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct spi_controller *ctlr;
|
||||
struct mtk_nor *sp;
|
||||
|
||||
ctlr = dev_get_drvdata(&pdev->dev);
|
||||
sp = spi_controller_get_devdata(ctlr);
|
||||
|
||||
mtk_nor_disable_clk(sp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mtk_nor_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = mtk_nor_match,
|
||||
},
|
||||
.probe = mtk_nor_probe,
|
||||
.remove = mtk_nor_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(mtk_nor_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Mediatek SPI NOR controller driver");
|
||||
MODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
|
@ -0,0 +1,187 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// General Purpose SPI multiplexer
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mux/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define SPI_MUX_NO_CS ((unsigned int)-1)
|
||||
|
||||
/**
|
||||
* DOC: Driver description
|
||||
*
|
||||
* This driver supports a MUX on an SPI bus. This can be useful when you need
|
||||
* more chip selects than the hardware peripherals support, or than are
|
||||
* available in a particular board setup.
|
||||
*
|
||||
* The driver will create an additional SPI controller. Devices added under the
|
||||
* mux will be handled as 'chip selects' on this controller.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct spi_mux_priv - the basic spi_mux structure
|
||||
* @spi: pointer to the device struct attached to the parent
|
||||
* spi controller
|
||||
* @current_cs: The current chip select set in the mux
|
||||
* @child_msg_complete: The mux replaces the complete callback in the child's
|
||||
* message to its own callback; this field is used by the
|
||||
* driver to store the child's callback during a transfer
|
||||
* @child_msg_context: Used to store the child's context to the callback
|
||||
* @child_msg_dev: Used to store the spi_device pointer to the child
|
||||
* @mux: mux_control structure used to provide chip selects for
|
||||
* downstream spi devices
|
||||
*/
|
||||
struct spi_mux_priv {
|
||||
struct spi_device *spi;
|
||||
unsigned int current_cs;
|
||||
|
||||
void (*child_msg_complete)(void *context);
|
||||
void *child_msg_context;
|
||||
struct spi_device *child_msg_dev;
|
||||
struct mux_control *mux;
|
||||
};
|
||||
|
||||
/* should not get called when the parent controller is doing a transfer */
|
||||
static int spi_mux_select(struct spi_device *spi)
|
||||
{
|
||||
struct spi_mux_priv *priv = spi_controller_get_devdata(spi->controller);
|
||||
int ret;
|
||||
|
||||
if (priv->current_cs == spi->chip_select)
|
||||
return 0;
|
||||
|
||||
dev_dbg(&priv->spi->dev, "setting up the mux for cs %d\n",
|
||||
spi->chip_select);
|
||||
|
||||
/* copy the child device's settings except for the cs */
|
||||
priv->spi->max_speed_hz = spi->max_speed_hz;
|
||||
priv->spi->mode = spi->mode;
|
||||
priv->spi->bits_per_word = spi->bits_per_word;
|
||||
|
||||
ret = mux_control_select(priv->mux, spi->chip_select);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->current_cs = spi->chip_select;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_mux_setup(struct spi_device *spi)
|
||||
{
|
||||
struct spi_mux_priv *priv = spi_controller_get_devdata(spi->controller);
|
||||
|
||||
/*
|
||||
* can be called multiple times, won't do a valid setup now but we will
|
||||
* change the settings when we do a transfer (necessary because we
|
||||
* can't predict from which device it will be anyway)
|
||||
*/
|
||||
return spi_setup(priv->spi);
|
||||
}
|
||||
|
||||
static void spi_mux_complete_cb(void *context)
|
||||
{
|
||||
struct spi_mux_priv *priv = (struct spi_mux_priv *)context;
|
||||
struct spi_controller *ctlr = spi_get_drvdata(priv->spi);
|
||||
struct spi_message *m = ctlr->cur_msg;
|
||||
|
||||
m->complete = priv->child_msg_complete;
|
||||
m->context = priv->child_msg_context;
|
||||
m->spi = priv->child_msg_dev;
|
||||
spi_finalize_current_message(ctlr);
|
||||
mux_control_deselect(priv->mux);
|
||||
}
|
||||
|
||||
static int spi_mux_transfer_one_message(struct spi_controller *ctlr,
|
||||
struct spi_message *m)
|
||||
{
|
||||
struct spi_mux_priv *priv = spi_controller_get_devdata(ctlr);
|
||||
struct spi_device *spi = m->spi;
|
||||
int ret;
|
||||
|
||||
ret = spi_mux_select(spi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Replace the complete callback, context and spi_device with our own
|
||||
* pointers. Save originals
|
||||
*/
|
||||
priv->child_msg_complete = m->complete;
|
||||
priv->child_msg_context = m->context;
|
||||
priv->child_msg_dev = m->spi;
|
||||
|
||||
m->complete = spi_mux_complete_cb;
|
||||
m->context = priv;
|
||||
m->spi = priv->spi;
|
||||
|
||||
/* do the transfer */
|
||||
return spi_async(priv->spi, m);
|
||||
}
|
||||
|
||||
static int spi_mux_probe(struct spi_device *spi)
|
||||
{
|
||||
struct spi_controller *ctlr;
|
||||
struct spi_mux_priv *priv;
|
||||
int ret;
|
||||
|
||||
ctlr = spi_alloc_master(&spi->dev, sizeof(*priv));
|
||||
if (!ctlr)
|
||||
return -ENOMEM;
|
||||
|
||||
spi_set_drvdata(spi, ctlr);
|
||||
priv = spi_controller_get_devdata(ctlr);
|
||||
priv->spi = spi;
|
||||
|
||||
priv->mux = devm_mux_control_get(&spi->dev, NULL);
|
||||
if (IS_ERR(priv->mux)) {
|
||||
ret = PTR_ERR(priv->mux);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&spi->dev, "failed to get control-mux\n");
|
||||
goto err_put_ctlr;
|
||||
}
|
||||
|
||||
priv->current_cs = SPI_MUX_NO_CS;
|
||||
|
||||
/* supported modes are the same as our parent's */
|
||||
ctlr->mode_bits = spi->controller->mode_bits;
|
||||
ctlr->flags = spi->controller->flags;
|
||||
ctlr->transfer_one_message = spi_mux_transfer_one_message;
|
||||
ctlr->setup = spi_mux_setup;
|
||||
ctlr->num_chipselect = mux_control_states(priv->mux);
|
||||
ctlr->bus_num = -1;
|
||||
ctlr->dev.of_node = spi->dev.of_node;
|
||||
|
||||
ret = devm_spi_register_controller(&spi->dev, ctlr);
|
||||
if (ret)
|
||||
goto err_put_ctlr;
|
||||
|
||||
return 0;
|
||||
|
||||
err_put_ctlr:
|
||||
spi_controller_put(ctlr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id spi_mux_of_match[] = {
|
||||
{ .compatible = "spi-mux" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct spi_driver spi_mux_driver = {
|
||||
.probe = spi_mux_probe,
|
||||
.driver = {
|
||||
.name = "spi-mux",
|
||||
.of_match_table = spi_mux_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_spi_driver(spi_mux_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SPI multiplexer");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -22,7 +22,6 @@
|
|||
#include <linux/ioport.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
@ -32,7 +31,7 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/module.h>
|
||||
|
|
|
@ -307,6 +307,7 @@
|
|||
|
||||
#define POLL_TOUT 5000
|
||||
#define NXP_FSPI_MAX_CHIPSELECT 4
|
||||
#define NXP_FSPI_MIN_IOMAP SZ_4M
|
||||
|
||||
struct nxp_fspi_devtype_data {
|
||||
unsigned int rxfifo;
|
||||
|
@ -324,11 +325,29 @@ static const struct nxp_fspi_devtype_data lx2160a_data = {
|
|||
.little_endian = true, /* little-endian */
|
||||
};
|
||||
|
||||
static const struct nxp_fspi_devtype_data imx8mm_data = {
|
||||
.rxfifo = SZ_512, /* (64 * 64 bits) */
|
||||
.txfifo = SZ_1K, /* (128 * 64 bits) */
|
||||
.ahb_buf_size = SZ_2K, /* (256 * 64 bits) */
|
||||
.quirks = 0,
|
||||
.little_endian = true, /* little-endian */
|
||||
};
|
||||
|
||||
static const struct nxp_fspi_devtype_data imx8qxp_data = {
|
||||
.rxfifo = SZ_512, /* (64 * 64 bits) */
|
||||
.txfifo = SZ_1K, /* (128 * 64 bits) */
|
||||
.ahb_buf_size = SZ_2K, /* (256 * 64 bits) */
|
||||
.quirks = 0,
|
||||
.little_endian = true, /* little-endian */
|
||||
};
|
||||
|
||||
struct nxp_fspi {
|
||||
void __iomem *iobase;
|
||||
void __iomem *ahb_addr;
|
||||
u32 memmap_phy;
|
||||
u32 memmap_phy_size;
|
||||
u32 memmap_start;
|
||||
u32 memmap_len;
|
||||
struct clk *clk, *clk_en;
|
||||
struct device *dev;
|
||||
struct completion c;
|
||||
|
@ -641,12 +660,35 @@ static void nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi)
|
|||
f->selected = spi->chip_select;
|
||||
}
|
||||
|
||||
static void nxp_fspi_read_ahb(struct nxp_fspi *f, const struct spi_mem_op *op)
|
||||
static int nxp_fspi_read_ahb(struct nxp_fspi *f, const struct spi_mem_op *op)
|
||||
{
|
||||
u32 start = op->addr.val;
|
||||
u32 len = op->data.nbytes;
|
||||
|
||||
/* if necessary, ioremap before AHB read */
|
||||
if ((!f->ahb_addr) || start < f->memmap_start ||
|
||||
start + len > f->memmap_start + f->memmap_len) {
|
||||
if (f->ahb_addr)
|
||||
iounmap(f->ahb_addr);
|
||||
|
||||
f->memmap_start = start;
|
||||
f->memmap_len = len > NXP_FSPI_MIN_IOMAP ?
|
||||
len : NXP_FSPI_MIN_IOMAP;
|
||||
|
||||
f->ahb_addr = ioremap_wc(f->memmap_phy + f->memmap_start,
|
||||
f->memmap_len);
|
||||
|
||||
if (!f->ahb_addr) {
|
||||
dev_err(f->dev, "failed to alloc memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read out the data directly from the AHB buffer. */
|
||||
memcpy_fromio(op->data.buf.in, (f->ahb_addr + op->addr.val), len);
|
||||
memcpy_fromio(op->data.buf.in,
|
||||
f->ahb_addr + start - f->memmap_start, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nxp_fspi_fill_txfifo(struct nxp_fspi *f,
|
||||
|
@ -806,7 +848,7 @@ static int nxp_fspi_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
|
|||
*/
|
||||
if (op->data.nbytes > (f->devtype_data->rxfifo - 4) &&
|
||||
op->data.dir == SPI_MEM_DATA_IN) {
|
||||
nxp_fspi_read_ahb(f, op);
|
||||
err = nxp_fspi_read_ahb(f, op);
|
||||
} else {
|
||||
if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT)
|
||||
nxp_fspi_fill_txfifo(f, op);
|
||||
|
@ -871,7 +913,8 @@ static int nxp_fspi_default_setup(struct nxp_fspi *f)
|
|||
fspi_writel(f, FSPI_DLLBCR_OVRDEN, base + FSPI_DLLBCR);
|
||||
|
||||
/* enable module */
|
||||
fspi_writel(f, FSPI_MCR0_AHB_TIMEOUT(0xFF) | FSPI_MCR0_IP_TIMEOUT(0xFF),
|
||||
fspi_writel(f, FSPI_MCR0_AHB_TIMEOUT(0xFF) |
|
||||
FSPI_MCR0_IP_TIMEOUT(0xFF) | (u32) FSPI_MCR0_OCTCOMB_EN,
|
||||
base + FSPI_MCR0);
|
||||
|
||||
/*
|
||||
|
@ -976,9 +1019,8 @@ static int nxp_fspi_probe(struct platform_device *pdev)
|
|||
|
||||
/* find the resources - controller memory mapped space */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fspi_mmap");
|
||||
f->ahb_addr = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(f->ahb_addr)) {
|
||||
ret = PTR_ERR(f->ahb_addr);
|
||||
if (!res) {
|
||||
ret = -ENODEV;
|
||||
goto err_put_ctrl;
|
||||
}
|
||||
|
||||
|
@ -1057,6 +1099,9 @@ static int nxp_fspi_remove(struct platform_device *pdev)
|
|||
|
||||
mutex_destroy(&f->lock);
|
||||
|
||||
if (f->ahb_addr)
|
||||
iounmap(f->ahb_addr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1076,6 +1121,8 @@ static int nxp_fspi_resume(struct device *dev)
|
|||
|
||||
static const struct of_device_id nxp_fspi_dt_ids[] = {
|
||||
{ .compatible = "nxp,lx2160a-fspi", .data = (void *)&lx2160a_data, },
|
||||
{ .compatible = "nxp,imx8mm-fspi", .data = (void *)&imx8mm_data, },
|
||||
{ .compatible = "nxp,imx8qxp-fspi", .data = (void *)&imx8qxp_data, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, nxp_fspi_dt_ids);
|
||||
|
|
|
@ -192,6 +192,11 @@ static bool is_quark_x1000_ssp(const struct driver_data *drv_data)
|
|||
return drv_data->ssp_type == QUARK_X1000_SSP;
|
||||
}
|
||||
|
||||
static bool is_mmp2_ssp(const struct driver_data *drv_data)
|
||||
{
|
||||
return drv_data->ssp_type == MMP2_SSP;
|
||||
}
|
||||
|
||||
static u32 pxa2xx_spi_get_ssrc1_change_mask(const struct driver_data *drv_data)
|
||||
{
|
||||
switch (drv_data->ssp_type) {
|
||||
|
@ -486,8 +491,8 @@ int pxa2xx_spi_flush(struct driver_data *drv_data)
|
|||
|
||||
static void pxa2xx_spi_off(struct driver_data *drv_data)
|
||||
{
|
||||
/* On MMP, disabling SSE seems to corrupt the rx fifo */
|
||||
if (drv_data->ssp_type == MMP2_SSP)
|
||||
/* On MMP, disabling SSE seems to corrupt the Rx FIFO */
|
||||
if (is_mmp2_ssp(drv_data))
|
||||
return;
|
||||
|
||||
pxa2xx_spi_write(drv_data, SSCR0,
|
||||
|
@ -1093,7 +1098,7 @@ static int pxa2xx_spi_transfer_one(struct spi_controller *controller,
|
|||
|| (pxa2xx_spi_read(drv_data, SSCR1) & change_mask)
|
||||
!= (cr1 & change_mask)) {
|
||||
/* stop the SSP, and update the other bits */
|
||||
if (drv_data->ssp_type != MMP2_SSP)
|
||||
if (!is_mmp2_ssp(drv_data))
|
||||
pxa2xx_spi_write(drv_data, SSCR0, cr0 & ~SSCR0_SSE);
|
||||
if (!pxa25x_ssp_comp(drv_data))
|
||||
pxa2xx_spi_write(drv_data, SSTO, chip->timeout);
|
||||
|
@ -1107,7 +1112,7 @@ static int pxa2xx_spi_transfer_one(struct spi_controller *controller,
|
|||
pxa2xx_spi_write(drv_data, SSTO, chip->timeout);
|
||||
}
|
||||
|
||||
if (drv_data->ssp_type == MMP2_SSP) {
|
||||
if (is_mmp2_ssp(drv_data)) {
|
||||
u8 tx_level = (pxa2xx_spi_read(drv_data, SSSR)
|
||||
& SSSR_TFL_MASK) >> 8;
|
||||
|
||||
|
@ -1571,18 +1576,18 @@ pxa2xx_spi_init_pdata(struct platform_device *pdev)
|
|||
else if (pcidev_id)
|
||||
type = (enum pxa_ssp_type)pcidev_id->driver_data;
|
||||
else
|
||||
return NULL;
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ssp = &pdata->ssp;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ssp->mmio_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(ssp->mmio_base))
|
||||
return NULL;
|
||||
return ERR_CAST(ssp->mmio_base);
|
||||
|
||||
ssp->phys_base = res->start;
|
||||
|
||||
|
@ -1596,11 +1601,11 @@ pxa2xx_spi_init_pdata(struct platform_device *pdev)
|
|||
|
||||
ssp->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(ssp->clk))
|
||||
return NULL;
|
||||
return ERR_CAST(ssp->clk);
|
||||
|
||||
ssp->irq = platform_get_irq(pdev, 0);
|
||||
if (ssp->irq < 0)
|
||||
return NULL;
|
||||
return ERR_PTR(ssp->irq);
|
||||
|
||||
ssp->type = type;
|
||||
ssp->dev = &pdev->dev;
|
||||
|
@ -1657,9 +1662,9 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
|
|||
platform_info = dev_get_platdata(dev);
|
||||
if (!platform_info) {
|
||||
platform_info = pxa2xx_spi_init_pdata(pdev);
|
||||
if (!platform_info) {
|
||||
if (IS_ERR(platform_info)) {
|
||||
dev_err(&pdev->dev, "missing platform data\n");
|
||||
return -ENODEV;
|
||||
return PTR_ERR(platform_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1907,11 +1912,7 @@ out_error_controller_alloc:
|
|||
static int pxa2xx_spi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct driver_data *drv_data = platform_get_drvdata(pdev);
|
||||
struct ssp_device *ssp;
|
||||
|
||||
if (!drv_data)
|
||||
return 0;
|
||||
ssp = drv_data->ssp;
|
||||
struct ssp_device *ssp = drv_data->ssp;
|
||||
|
||||
pm_runtime_get_sync(&pdev->dev);
|
||||
|
||||
|
|
|
@ -843,14 +843,17 @@ static const struct dev_pm_ops rockchip_spi_pm = {
|
|||
};
|
||||
|
||||
static const struct of_device_id rockchip_spi_dt_match[] = {
|
||||
{ .compatible = "rockchip,rv1108-spi", },
|
||||
{ .compatible = "rockchip,px30-spi", },
|
||||
{ .compatible = "rockchip,rk3036-spi", },
|
||||
{ .compatible = "rockchip,rk3066-spi", },
|
||||
{ .compatible = "rockchip,rk3188-spi", },
|
||||
{ .compatible = "rockchip,rk3228-spi", },
|
||||
{ .compatible = "rockchip,rk3288-spi", },
|
||||
{ .compatible = "rockchip,rk3308-spi", },
|
||||
{ .compatible = "rockchip,rk3328-spi", },
|
||||
{ .compatible = "rockchip,rk3368-spi", },
|
||||
{ .compatible = "rockchip,rk3399-spi", },
|
||||
{ .compatible = "rockchip,rv1108-spi", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/sh_dma.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/rspi.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define RSPI_SPCR 0x00 /* Control Register */
|
||||
#define RSPI_SSLP 0x01 /* Slave Select Polarity Register */
|
||||
|
@ -79,8 +80,7 @@
|
|||
#define SPCR_BSWAP 0x01 /* Byte Swap of read-data for DMAC */
|
||||
|
||||
/* SSLP - Slave Select Polarity Register */
|
||||
#define SSLP_SSL1P 0x02 /* SSL1 Signal Polarity Setting */
|
||||
#define SSLP_SSL0P 0x01 /* SSL0 Signal Polarity Setting */
|
||||
#define SSLP_SSLP(i) BIT(i) /* SSLi Signal Polarity Setting */
|
||||
|
||||
/* SPPCR - Pin Control Register */
|
||||
#define SPPCR_MOIFE 0x20 /* MOSI Idle Value Fixing Enable */
|
||||
|
@ -181,7 +181,9 @@ struct rspi_data {
|
|||
void __iomem *addr;
|
||||
u32 max_speed_hz;
|
||||
struct spi_controller *ctlr;
|
||||
struct platform_device *pdev;
|
||||
wait_queue_head_t wait;
|
||||
spinlock_t lock; /* Protects RMW-access to RSPI_SSLP */
|
||||
struct clk *clk;
|
||||
u16 spcmd;
|
||||
u8 spsr;
|
||||
|
@ -239,7 +241,7 @@ struct spi_ops {
|
|||
int (*set_config_register)(struct rspi_data *rspi, int access_size);
|
||||
int (*transfer_one)(struct spi_controller *ctlr,
|
||||
struct spi_device *spi, struct spi_transfer *xfer);
|
||||
u16 mode_bits;
|
||||
u16 extra_mode_bits;
|
||||
u16 flags;
|
||||
u16 fifo_size;
|
||||
u8 num_hw_ss;
|
||||
|
@ -919,6 +921,29 @@ static int qspi_setup_sequencer(struct rspi_data *rspi,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int rspi_setup(struct spi_device *spi)
|
||||
{
|
||||
struct rspi_data *rspi = spi_controller_get_devdata(spi->controller);
|
||||
u8 sslp;
|
||||
|
||||
if (spi->cs_gpiod)
|
||||
return 0;
|
||||
|
||||
pm_runtime_get_sync(&rspi->pdev->dev);
|
||||
spin_lock_irq(&rspi->lock);
|
||||
|
||||
sslp = rspi_read8(rspi, RSPI_SSLP);
|
||||
if (spi->mode & SPI_CS_HIGH)
|
||||
sslp |= SSLP_SSLP(spi->chip_select);
|
||||
else
|
||||
sslp &= ~SSLP_SSLP(spi->chip_select);
|
||||
rspi_write8(rspi, sslp, RSPI_SSLP);
|
||||
|
||||
spin_unlock_irq(&rspi->lock);
|
||||
pm_runtime_put(&rspi->pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rspi_prepare_message(struct spi_controller *ctlr,
|
||||
struct spi_message *msg)
|
||||
{
|
||||
|
@ -933,6 +958,8 @@ static int rspi_prepare_message(struct spi_controller *ctlr,
|
|||
rspi->spcmd |= SPCMD_CPOL;
|
||||
if (spi->mode & SPI_CPHA)
|
||||
rspi->spcmd |= SPCMD_CPHA;
|
||||
if (spi->mode & SPI_LSB_FIRST)
|
||||
rspi->spcmd |= SPCMD_LSBF;
|
||||
|
||||
/* Configure slave signal to assert */
|
||||
rspi->spcmd |= SPCMD_SSLA(spi->cs_gpiod ? rspi->ctlr->unused_native_cs
|
||||
|
@ -1122,7 +1149,6 @@ static int rspi_remove(struct platform_device *pdev)
|
|||
static const struct spi_ops rspi_ops = {
|
||||
.set_config_register = rspi_set_config_register,
|
||||
.transfer_one = rspi_transfer_one,
|
||||
.mode_bits = SPI_CPHA | SPI_CPOL | SPI_LOOP,
|
||||
.flags = SPI_CONTROLLER_MUST_TX,
|
||||
.fifo_size = 8,
|
||||
.num_hw_ss = 2,
|
||||
|
@ -1131,7 +1157,6 @@ static const struct spi_ops rspi_ops = {
|
|||
static const struct spi_ops rspi_rz_ops = {
|
||||
.set_config_register = rspi_rz_set_config_register,
|
||||
.transfer_one = rspi_rz_transfer_one,
|
||||
.mode_bits = SPI_CPHA | SPI_CPOL | SPI_LOOP,
|
||||
.flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX,
|
||||
.fifo_size = 8, /* 8 for TX, 32 for RX */
|
||||
.num_hw_ss = 1,
|
||||
|
@ -1140,8 +1165,7 @@ static const struct spi_ops rspi_rz_ops = {
|
|||
static const struct spi_ops qspi_ops = {
|
||||
.set_config_register = qspi_set_config_register,
|
||||
.transfer_one = qspi_transfer_one,
|
||||
.mode_bits = SPI_CPHA | SPI_CPOL | SPI_LOOP |
|
||||
SPI_TX_DUAL | SPI_TX_QUAD |
|
||||
.extra_mode_bits = SPI_TX_DUAL | SPI_TX_QUAD |
|
||||
SPI_RX_DUAL | SPI_RX_QUAD,
|
||||
.flags = SPI_CONTROLLER_MUST_RX | SPI_CONTROLLER_MUST_TX,
|
||||
.fifo_size = 32,
|
||||
|
@ -1249,16 +1273,20 @@ static int rspi_probe(struct platform_device *pdev)
|
|||
goto error1;
|
||||
}
|
||||
|
||||
rspi->pdev = pdev;
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
init_waitqueue_head(&rspi->wait);
|
||||
spin_lock_init(&rspi->lock);
|
||||
|
||||
ctlr->bus_num = pdev->id;
|
||||
ctlr->setup = rspi_setup;
|
||||
ctlr->auto_runtime_pm = true;
|
||||
ctlr->transfer_one = ops->transfer_one;
|
||||
ctlr->prepare_message = rspi_prepare_message;
|
||||
ctlr->unprepare_message = rspi_unprepare_message;
|
||||
ctlr->mode_bits = ops->mode_bits;
|
||||
ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST |
|
||||
SPI_LOOP | ops->extra_mode_bits;
|
||||
ctlr->flags = ops->flags;
|
||||
ctlr->dev.of_node = pdev->dev.of_node;
|
||||
ctlr->use_gpio_descriptors = true;
|
||||
|
|
|
@ -227,7 +227,7 @@ static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
|
|||
struct spi_fiq_code {
|
||||
u32 length;
|
||||
u32 ack_offset;
|
||||
u8 data[0];
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
extern struct spi_fiq_code s3c24xx_spi_fiq_txrx;
|
||||
|
|
|
@ -565,7 +565,7 @@ static int stm32_qspi_probe(struct platform_device *pdev)
|
|||
qspi->io_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(qspi->io_base)) {
|
||||
ret = PTR_ERR(qspi->io_base);
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
qspi->phys_base = res->start;
|
||||
|
@ -574,24 +574,26 @@ static int stm32_qspi_probe(struct platform_device *pdev)
|
|||
qspi->mm_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(qspi->mm_base)) {
|
||||
ret = PTR_ERR(qspi->mm_base);
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
qspi->mm_size = resource_size(res);
|
||||
if (qspi->mm_size > STM32_QSPI_MAX_MMAP_SZ) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
if (irq < 0) {
|
||||
ret = irq;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, stm32_qspi_irq, 0,
|
||||
dev_name(dev), qspi);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to request irq\n");
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
init_completion(&qspi->data_completion);
|
||||
|
@ -599,23 +601,27 @@ static int stm32_qspi_probe(struct platform_device *pdev)
|
|||
qspi->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(qspi->clk)) {
|
||||
ret = PTR_ERR(qspi->clk);
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
qspi->clk_rate = clk_get_rate(qspi->clk);
|
||||
if (!qspi->clk_rate) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(qspi->clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "can not enable the clock\n");
|
||||
goto err;
|
||||
goto err_master_put;
|
||||
}
|
||||
|
||||
rstc = devm_reset_control_get_exclusive(dev, NULL);
|
||||
if (!IS_ERR(rstc)) {
|
||||
if (IS_ERR(rstc)) {
|
||||
ret = PTR_ERR(rstc);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto err_qspi_release;
|
||||
} else {
|
||||
reset_control_assert(rstc);
|
||||
udelay(2);
|
||||
reset_control_deassert(rstc);
|
||||
|
@ -625,7 +631,7 @@ static int stm32_qspi_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, qspi);
|
||||
ret = stm32_qspi_dma_setup(qspi);
|
||||
if (ret)
|
||||
goto err;
|
||||
goto err_qspi_release;
|
||||
|
||||
mutex_init(&qspi->lock);
|
||||
|
||||
|
@ -641,8 +647,9 @@ static int stm32_qspi_probe(struct platform_device *pdev)
|
|||
if (!ret)
|
||||
return 0;
|
||||
|
||||
err:
|
||||
err_qspi_release:
|
||||
stm32_qspi_release(qspi);
|
||||
err_master_put:
|
||||
spi_master_put(qspi->ctrl);
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
#define SPI_DMA_MIN_BYTES 16
|
||||
|
||||
/**
|
||||
* stm32_spi_reg - stm32 SPI register & bitfield desc
|
||||
* struct stm32_spi_reg - stm32 SPI register & bitfield desc
|
||||
* @reg: register offset
|
||||
* @mask: bitfield mask
|
||||
* @shift: left shift
|
||||
|
@ -187,16 +187,16 @@ struct stm32_spi_reg {
|
|||
};
|
||||
|
||||
/**
|
||||
* stm32_spi_regspec - stm32 registers definition, compatible dependent data
|
||||
* en: enable register and SPI enable bit
|
||||
* dma_rx_en: SPI DMA RX enable register end SPI DMA RX enable bit
|
||||
* dma_tx_en: SPI DMA TX enable register end SPI DMA TX enable bit
|
||||
* cpol: clock polarity register and polarity bit
|
||||
* cpha: clock phase register and phase bit
|
||||
* lsb_first: LSB transmitted first register and bit
|
||||
* br: baud rate register and bitfields
|
||||
* rx: SPI RX data register
|
||||
* tx: SPI TX data register
|
||||
* struct stm32_spi_regspec - stm32 registers definition, compatible dependent data
|
||||
* @en: enable register and SPI enable bit
|
||||
* @dma_rx_en: SPI DMA RX enable register end SPI DMA RX enable bit
|
||||
* @dma_tx_en: SPI DMA TX enable register end SPI DMA TX enable bit
|
||||
* @cpol: clock polarity register and polarity bit
|
||||
* @cpha: clock phase register and phase bit
|
||||
* @lsb_first: LSB transmitted first register and bit
|
||||
* @br: baud rate register and bitfields
|
||||
* @rx: SPI RX data register
|
||||
* @tx: SPI TX data register
|
||||
*/
|
||||
struct stm32_spi_regspec {
|
||||
const struct stm32_spi_reg en;
|
||||
|
@ -213,7 +213,7 @@ struct stm32_spi_regspec {
|
|||
struct stm32_spi;
|
||||
|
||||
/**
|
||||
* stm32_spi_cfg - stm32 compatible configuration data
|
||||
* struct stm32_spi_cfg - stm32 compatible configuration data
|
||||
* @regs: registers descriptions
|
||||
* @get_fifo_size: routine to get fifo size
|
||||
* @get_bpw_mask: routine to get bits per word mask
|
||||
|
@ -223,13 +223,13 @@ struct stm32_spi;
|
|||
* @set_mode: routine to configure registers to desired mode
|
||||
* @set_data_idleness: optional routine to configure registers to desired idle
|
||||
* time between frames (if driver has this functionality)
|
||||
* set_number_of_data: optional routine to configure registers to desired
|
||||
* @set_number_of_data: optional routine to configure registers to desired
|
||||
* number of data (if driver has this functionality)
|
||||
* @can_dma: routine to determine if the transfer is eligible for DMA use
|
||||
* @transfer_one_dma_start: routine to start transfer a single spi_transfer
|
||||
* using DMA
|
||||
* @dma_rx cb: routine to call after DMA RX channel operation is complete
|
||||
* @dma_tx cb: routine to call after DMA TX channel operation is complete
|
||||
* @dma_rx_cb: routine to call after DMA RX channel operation is complete
|
||||
* @dma_tx_cb: routine to call after DMA TX channel operation is complete
|
||||
* @transfer_one_irq: routine to configure interrupts for driver
|
||||
* @irq_handler_event: Interrupt handler for SPI controller events
|
||||
* @irq_handler_thread: thread of interrupt handler for SPI controller
|
||||
|
@ -587,6 +587,7 @@ static void stm32f4_spi_read_rx(struct stm32_spi *spi)
|
|||
/**
|
||||
* stm32h7_spi_read_rxfifo - Read bytes in Receive Data Register
|
||||
* @spi: pointer to the spi controller data structure
|
||||
* @flush: boolean indicating that FIFO should be flushed
|
||||
*
|
||||
* Write in rx_buf depends on remaining bytes to avoid to write beyond
|
||||
* rx_buf end.
|
||||
|
@ -756,6 +757,9 @@ static void stm32h7_spi_disable(struct stm32_spi *spi)
|
|||
|
||||
/**
|
||||
* stm32_spi_can_dma - Determine if the transfer is eligible for DMA use
|
||||
* @master: controller master interface
|
||||
* @spi_dev: pointer to the spi device
|
||||
* @transfer: pointer to spi transfer
|
||||
*
|
||||
* If driver has fifo and the current transfer size is greater than fifo size,
|
||||
* use DMA. Otherwise use DMA for transfer longer than defined DMA min bytes.
|
||||
|
@ -974,6 +978,8 @@ static irqreturn_t stm32h7_spi_irq_thread(int irq, void *dev_id)
|
|||
|
||||
/**
|
||||
* stm32_spi_prepare_msg - set up the controller to transfer a single message
|
||||
* @master: controller master interface
|
||||
* @msg: pointer to spi message
|
||||
*/
|
||||
static int stm32_spi_prepare_msg(struct spi_master *master,
|
||||
struct spi_message *msg)
|
||||
|
@ -1026,6 +1032,7 @@ static int stm32_spi_prepare_msg(struct spi_master *master,
|
|||
|
||||
/**
|
||||
* stm32f4_spi_dma_tx_cb - dma callback
|
||||
* @data: pointer to the spi controller data structure
|
||||
*
|
||||
* DMA callback is called when the transfer is complete for DMA TX channel.
|
||||
*/
|
||||
|
@ -1041,6 +1048,7 @@ static void stm32f4_spi_dma_tx_cb(void *data)
|
|||
|
||||
/**
|
||||
* stm32f4_spi_dma_rx_cb - dma callback
|
||||
* @data: pointer to the spi controller data structure
|
||||
*
|
||||
* DMA callback is called when the transfer is complete for DMA RX channel.
|
||||
*/
|
||||
|
@ -1054,6 +1062,7 @@ static void stm32f4_spi_dma_rx_cb(void *data)
|
|||
|
||||
/**
|
||||
* stm32h7_spi_dma_cb - dma callback
|
||||
* @data: pointer to the spi controller data structure
|
||||
*
|
||||
* DMA callback is called when the transfer is complete or when an error
|
||||
* occurs. If the transfer is complete, EOT flag is raised.
|
||||
|
@ -1079,6 +1088,9 @@ static void stm32h7_spi_dma_cb(void *data)
|
|||
/**
|
||||
* stm32_spi_dma_config - configure dma slave channel depending on current
|
||||
* transfer bits_per_word.
|
||||
* @spi: pointer to the spi controller data structure
|
||||
* @dma_conf: pointer to the dma_slave_config structure
|
||||
* @dir: direction of the dma transfer
|
||||
*/
|
||||
static void stm32_spi_dma_config(struct stm32_spi *spi,
|
||||
struct dma_slave_config *dma_conf,
|
||||
|
@ -1126,6 +1138,7 @@ static void stm32_spi_dma_config(struct stm32_spi *spi,
|
|||
/**
|
||||
* stm32f4_spi_transfer_one_irq - transfer a single spi_transfer using
|
||||
* interrupts
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*
|
||||
* It must returns 0 if the transfer is finished or 1 if the transfer is still
|
||||
* in progress.
|
||||
|
@ -1166,6 +1179,7 @@ static int stm32f4_spi_transfer_one_irq(struct stm32_spi *spi)
|
|||
/**
|
||||
* stm32h7_spi_transfer_one_irq - transfer a single spi_transfer using
|
||||
* interrupts
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*
|
||||
* It must returns 0 if the transfer is finished or 1 if the transfer is still
|
||||
* in progress.
|
||||
|
@ -1207,6 +1221,7 @@ static int stm32h7_spi_transfer_one_irq(struct stm32_spi *spi)
|
|||
/**
|
||||
* stm32f4_spi_transfer_one_dma_start - Set SPI driver registers to start
|
||||
* transfer using DMA
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*/
|
||||
static void stm32f4_spi_transfer_one_dma_start(struct stm32_spi *spi)
|
||||
{
|
||||
|
@ -1227,6 +1242,7 @@ static void stm32f4_spi_transfer_one_dma_start(struct stm32_spi *spi)
|
|||
/**
|
||||
* stm32h7_spi_transfer_one_dma_start - Set SPI driver registers to start
|
||||
* transfer using DMA
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*/
|
||||
static void stm32h7_spi_transfer_one_dma_start(struct stm32_spi *spi)
|
||||
{
|
||||
|
@ -1243,6 +1259,8 @@ static void stm32h7_spi_transfer_one_dma_start(struct stm32_spi *spi)
|
|||
|
||||
/**
|
||||
* stm32_spi_transfer_one_dma - transfer a single spi_transfer using DMA
|
||||
* @spi: pointer to the spi controller data structure
|
||||
* @xfer: pointer to the spi_transfer structure
|
||||
*
|
||||
* It must returns 0 if the transfer is finished or 1 if the transfer is still
|
||||
* in progress.
|
||||
|
@ -1405,7 +1423,7 @@ static void stm32_spi_set_mbr(struct stm32_spi *spi, u32 mbrdiv)
|
|||
/**
|
||||
* stm32_spi_communication_type - return transfer communication type
|
||||
* @spi_dev: pointer to the spi device
|
||||
* transfer: pointer to spi transfer
|
||||
* @transfer: pointer to spi transfer
|
||||
*/
|
||||
static unsigned int stm32_spi_communication_type(struct spi_device *spi_dev,
|
||||
struct spi_transfer *transfer)
|
||||
|
@ -1522,7 +1540,7 @@ static void stm32h7_spi_data_idleness(struct stm32_spi *spi, u32 len)
|
|||
/**
|
||||
* stm32h7_spi_number_of_data - configure number of data at current transfer
|
||||
* @spi: pointer to the spi controller data structure
|
||||
* @len: transfer length
|
||||
* @nb_words: transfer length (in words)
|
||||
*/
|
||||
static int stm32h7_spi_number_of_data(struct stm32_spi *spi, u32 nb_words)
|
||||
{
|
||||
|
@ -1546,6 +1564,9 @@ static int stm32h7_spi_number_of_data(struct stm32_spi *spi, u32 nb_words)
|
|||
* stm32_spi_transfer_one_setup - common setup to transfer a single
|
||||
* spi_transfer either using DMA or
|
||||
* interrupts.
|
||||
* @spi: pointer to the spi controller data structure
|
||||
* @spi_dev: pointer to the spi device
|
||||
* @transfer: pointer to spi transfer
|
||||
*/
|
||||
static int stm32_spi_transfer_one_setup(struct stm32_spi *spi,
|
||||
struct spi_device *spi_dev,
|
||||
|
@ -1625,6 +1646,9 @@ out:
|
|||
|
||||
/**
|
||||
* stm32_spi_transfer_one - transfer a single spi_transfer
|
||||
* @master: controller master interface
|
||||
* @spi_dev: pointer to the spi device
|
||||
* @transfer: pointer to spi transfer
|
||||
*
|
||||
* It must return 0 if the transfer is finished or 1 if the transfer is still
|
||||
* in progress.
|
||||
|
@ -1658,6 +1682,8 @@ static int stm32_spi_transfer_one(struct spi_master *master,
|
|||
|
||||
/**
|
||||
* stm32_spi_unprepare_msg - relax the hardware
|
||||
* @master: controller master interface
|
||||
* @msg: pointer to the spi message
|
||||
*/
|
||||
static int stm32_spi_unprepare_msg(struct spi_master *master,
|
||||
struct spi_message *msg)
|
||||
|
@ -1671,6 +1697,7 @@ static int stm32_spi_unprepare_msg(struct spi_master *master,
|
|||
|
||||
/**
|
||||
* stm32f4_spi_config - Configure SPI controller as SPI master
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*/
|
||||
static int stm32f4_spi_config(struct stm32_spi *spi)
|
||||
{
|
||||
|
@ -1701,6 +1728,7 @@ static int stm32f4_spi_config(struct stm32_spi *spi)
|
|||
|
||||
/**
|
||||
* stm32h7_spi_config - Configure SPI controller as SPI master
|
||||
* @spi: pointer to the spi controller data structure
|
||||
*/
|
||||
static int stm32h7_spi_config(struct stm32_spi *spi)
|
||||
{
|
||||
|
|
|
@ -510,6 +510,7 @@ struct spi_device *spi_alloc_device(struct spi_controller *ctlr)
|
|||
spi->dev.bus = &spi_bus_type;
|
||||
spi->dev.release = spidev_release;
|
||||
spi->cs_gpio = -ENOENT;
|
||||
spi->mode = ctlr->buswidth_override_bits;
|
||||
|
||||
spin_lock_init(&spi->statistics.lock);
|
||||
|
||||
|
@ -1514,17 +1515,15 @@ void spi_take_timestamp_pre(struct spi_controller *ctlr,
|
|||
if (!xfer->ptp_sts)
|
||||
return;
|
||||
|
||||
if (xfer->timestamped_pre)
|
||||
if (xfer->timestamped)
|
||||
return;
|
||||
|
||||
if (progress < xfer->ptp_sts_word_pre)
|
||||
if (progress > xfer->ptp_sts_word_pre)
|
||||
return;
|
||||
|
||||
/* Capture the resolution of the timestamp */
|
||||
xfer->ptp_sts_word_pre = progress;
|
||||
|
||||
xfer->timestamped_pre = true;
|
||||
|
||||
if (irqs_off) {
|
||||
local_irq_save(ctlr->irq_flags);
|
||||
preempt_disable();
|
||||
|
@ -1553,7 +1552,7 @@ void spi_take_timestamp_post(struct spi_controller *ctlr,
|
|||
if (!xfer->ptp_sts)
|
||||
return;
|
||||
|
||||
if (xfer->timestamped_post)
|
||||
if (xfer->timestamped)
|
||||
return;
|
||||
|
||||
if (progress < xfer->ptp_sts_word_post)
|
||||
|
@ -1569,7 +1568,7 @@ void spi_take_timestamp_post(struct spi_controller *ctlr,
|
|||
/* Capture the resolution of the timestamp */
|
||||
xfer->ptp_sts_word_post = progress;
|
||||
|
||||
xfer->timestamped_post = true;
|
||||
xfer->timestamped = true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_take_timestamp_post);
|
||||
|
||||
|
@ -1674,12 +1673,9 @@ void spi_finalize_current_message(struct spi_controller *ctlr)
|
|||
}
|
||||
}
|
||||
|
||||
if (unlikely(ctlr->ptp_sts_supported)) {
|
||||
list_for_each_entry(xfer, &mesg->transfers, transfer_list) {
|
||||
WARN_ON_ONCE(xfer->ptp_sts && !xfer->timestamped_pre);
|
||||
WARN_ON_ONCE(xfer->ptp_sts && !xfer->timestamped_post);
|
||||
}
|
||||
}
|
||||
if (unlikely(ctlr->ptp_sts_supported))
|
||||
list_for_each_entry(xfer, &mesg->transfers, transfer_list)
|
||||
WARN_ON_ONCE(xfer->ptp_sts && !xfer->timestamped);
|
||||
|
||||
spi_unmap_msg(ctlr, mesg);
|
||||
|
||||
|
@ -1955,12 +1951,7 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
|
|||
spi->mode |= SPI_CS_HIGH;
|
||||
|
||||
/* Device speed */
|
||||
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
|
||||
if (rc) {
|
||||
dev_err(&ctlr->dev,
|
||||
"%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc);
|
||||
return rc;
|
||||
}
|
||||
if (!of_property_read_u32(nc, "spi-max-frequency", &value))
|
||||
spi->max_speed_hz = value;
|
||||
|
||||
return 0;
|
||||
|
@ -2181,9 +2172,10 @@ static acpi_status acpi_register_spi_device(struct spi_controller *ctlr,
|
|||
return AE_NO_MEMORY;
|
||||
}
|
||||
|
||||
|
||||
ACPI_COMPANION_SET(&spi->dev, adev);
|
||||
spi->max_speed_hz = lookup.max_speed_hz;
|
||||
spi->mode = lookup.mode;
|
||||
spi->mode |= lookup.mode;
|
||||
spi->irq = lookup.irq;
|
||||
spi->bits_per_word = lookup.bits_per_word;
|
||||
spi->chip_select = lookup.chip_select;
|
||||
|
@ -4034,7 +4026,7 @@ static struct spi_device *acpi_spi_find_device_by_adev(struct acpi_device *adev)
|
|||
struct device *dev;
|
||||
|
||||
dev = bus_find_device_by_acpi_dev(&spi_bus_type, adev);
|
||||
return dev ? to_spi_device(dev) : NULL;
|
||||
return to_spi_device(dev);
|
||||
}
|
||||
|
||||
static int acpi_spi_notify(struct notifier_block *nb, unsigned long value,
|
||||
|
|
|
@ -275,14 +275,14 @@ static int spidev_message(struct spidev_data *spidev,
|
|||
#ifdef VERBOSE
|
||||
dev_dbg(&spidev->spi->dev,
|
||||
" xfer len %u %s%s%s%dbits %u usec %u usec %uHz\n",
|
||||
u_tmp->len,
|
||||
u_tmp->rx_buf ? "rx " : "",
|
||||
u_tmp->tx_buf ? "tx " : "",
|
||||
u_tmp->cs_change ? "cs " : "",
|
||||
u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
|
||||
u_tmp->delay_usecs,
|
||||
u_tmp->word_delay_usecs,
|
||||
u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
|
||||
k_tmp->len,
|
||||
k_tmp->rx_buf ? "rx " : "",
|
||||
k_tmp->tx_buf ? "tx " : "",
|
||||
k_tmp->cs_change ? "cs " : "",
|
||||
k_tmp->bits_per_word ? : spidev->spi->bits_per_word,
|
||||
k_tmp->delay.value,
|
||||
k_tmp->word_delay.value,
|
||||
k_tmp->speed_hz ? : spidev->spi->max_speed_hz);
|
||||
#endif
|
||||
spi_message_add_tail(k_tmp, &msg);
|
||||
}
|
||||
|
@ -454,10 +454,11 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||
|
||||
spi->max_speed_hz = tmp;
|
||||
retval = spi_setup(spi);
|
||||
if (retval >= 0)
|
||||
if (retval == 0) {
|
||||
spidev->speed_hz = tmp;
|
||||
else
|
||||
dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
|
||||
dev_dbg(&spi->dev, "%d Hz (max)\n",
|
||||
spidev->speed_hz);
|
||||
}
|
||||
spi->max_speed_hz = save;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -277,9 +277,9 @@ enum regulator_type {
|
|||
* @curr_table: Current limit mapping table (if table based mapping)
|
||||
*
|
||||
* @vsel_range_reg: Register for range selector when using pickable ranges
|
||||
* and regulator_regmap_X_voltage_X_pickable functions.
|
||||
* and ``regulator_map_*_voltage_*_pickable`` functions.
|
||||
* @vsel_range_mask: Mask for register bitfield used for range selector
|
||||
* @vsel_reg: Register for selector when using regulator_regmap_X_voltage_
|
||||
* @vsel_reg: Register for selector when using ``regulator_map_*_voltage_*``
|
||||
* @vsel_mask: Mask for register bitfield used for selector
|
||||
* @vsel_step: Specify the resolution of selector stepping when setting
|
||||
* voltage. If 0, then no stepping is done (requested selector is
|
||||
|
|
|
@ -10,6 +10,7 @@ struct qcom_smd_rpm;
|
|||
/*
|
||||
* Constants used for addressing resources in the RPM.
|
||||
*/
|
||||
#define QCOM_SMD_RPM_BBYB 0x62796262
|
||||
#define QCOM_SMD_RPM_BOBB 0x62626f62
|
||||
#define QCOM_SMD_RPM_BOOST 0x61747362
|
||||
#define QCOM_SMD_RPM_BUS_CLK 0x316b6c63
|
||||
|
|
|
@ -135,6 +135,8 @@ extern int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer);
|
|||
* @modalias: Name of the driver to use with this device, or an alias
|
||||
* for that name. This appears in the sysfs "modalias" attribute
|
||||
* for driver coldplugging, and in uevents used for hotplugging
|
||||
* @driver_override: If the name of a driver is written to this attribute, then
|
||||
* the device will bind to the named driver and only the named driver.
|
||||
* @cs_gpio: LEGACY: gpio number of the chipselect line (optional, -ENOENT when
|
||||
* not using a GPIO line) use cs_gpiod in new drivers by opting in on
|
||||
* the spi_master.
|
||||
|
@ -443,6 +445,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
|
|||
* @spi_transfer->ptp_sts_word_post were transmitted.
|
||||
* If the driver does not set this, the SPI core takes the snapshot as
|
||||
* close to the driver hand-over as possible.
|
||||
* @irq_flags: Interrupt enable state during PTP system timestamping
|
||||
*
|
||||
* Each SPI controller can communicate with one or more @spi_device
|
||||
* children. These make a small bus, sharing MOSI, MISO and SCK signals
|
||||
|
@ -481,6 +484,9 @@ struct spi_controller {
|
|||
/* spi_device.mode flags understood by this controller driver */
|
||||
u32 mode_bits;
|
||||
|
||||
/* spi_device.mode flags override flags for this controller */
|
||||
u32 buswidth_override_bits;
|
||||
|
||||
/* bitmask of supported bits_per_word for transfers */
|
||||
u32 bits_per_word_mask;
|
||||
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
|
||||
|
@ -930,8 +936,7 @@ struct spi_transfer {
|
|||
|
||||
struct ptp_system_timestamp *ptp_sts;
|
||||
|
||||
bool timestamped_pre;
|
||||
bool timestamped_post;
|
||||
bool timestamped;
|
||||
|
||||
struct list_head transfer_list;
|
||||
};
|
||||
|
|
|
@ -51,7 +51,7 @@ $(OUTPUT)spidev_fdx: $(SPIDEV_FDX_IN)
|
|||
|
||||
clean:
|
||||
rm -f $(ALL_PROGRAMS)
|
||||
rm -f $(OUTPUT)include/linux/spi/spidev.h
|
||||
rm -rf $(OUTPUT)include/
|
||||
find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete
|
||||
|
||||
install: $(ALL_PROGRAMS)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
|
@ -26,7 +27,11 @@
|
|||
|
||||
static void pabort(const char *s)
|
||||
{
|
||||
if (errno != 0)
|
||||
perror(s);
|
||||
else
|
||||
printf("%s\n", s);
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
|
@ -283,7 +288,6 @@ static void parse_opts(int argc, char *argv[])
|
|||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mode & SPI_LOOP) {
|
||||
|
@ -405,6 +409,9 @@ int main(int argc, char *argv[])
|
|||
|
||||
parse_opts(argc, argv);
|
||||
|
||||
if (input_tx && input_file)
|
||||
pabort("only one of -p and --input may be selected");
|
||||
|
||||
fd = open(device, O_RDWR);
|
||||
if (fd < 0)
|
||||
pabort("can't open device");
|
||||
|
@ -446,9 +453,6 @@ int main(int argc, char *argv[])
|
|||
printf("bits per word: %d\n", bits);
|
||||
printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
|
||||
|
||||
if (input_tx && input_file)
|
||||
pabort("only one of -p and --input may be selected");
|
||||
|
||||
if (input_tx)
|
||||
transfer_escaped_string(fd, input_tx);
|
||||
else if (input_file)
|
||||
|
|
Loading…
Reference in New Issue