* [PATCH v5 2/3] mmc: core: Add support for SDIO async interrupt
From: Axe Yang @ 2022-01-21 7:19 UTC (permalink / raw)
To: Ulf Hansson, Rob Herring, Chaotian Jing, Matthias Brugger,
Adrian Hunter
Cc: Yoshihiro Shimoda, Satya Tangirala, Andy Shevchenko, Wolfram Sang,
Axe Yang, Lucas Stach, Eric Biggers, Andrew Jeffery, Stephen Boyd,
Kiwoong Kim, Yue Hu, Tian Tao, angelogioacchino.delregno,
linux-mmc, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek
In-Reply-To: <20220121071942.11601-1-axe.yang@mediatek.com>
If cap-sdio-async-irq flag is set in host dts node, parse EAI
information from SDIO CCCR interrupt externsion segment. If async
interrupt is supported by SDIO card then send command to card to
enable it and set enable_async_irq flag in sdio_cccr structure to 1.
The parse flow is implemented in sdio_read_cccr().
Acked-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Axe Yang <axe.yang@mediatek.com>
---
drivers/mmc/core/host.c | 2 ++
drivers/mmc/core/sdio.c | 17 +++++++++++++++++
include/linux/mmc/card.h | 3 ++-
include/linux/mmc/host.h | 1 +
include/linux/mmc/sdio.h | 5 +++++
5 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index cf140f4ec864..a972241548b4 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -410,6 +410,8 @@ int mmc_of_parse(struct mmc_host *host)
if (device_property_read_bool(dev, "no-mmc-hs400"))
host->caps2 &= ~(MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V |
MMC_CAP2_HS400_ES);
+ if (device_property_read_bool(dev, "cap-sdio-async-irq"))
+ host->caps2 |= MMC_CAP2_SDIO_ASYNC_IRQ;
/* Must be after "non-removable" check */
if (device_property_read_u32(dev, "fixed-emmc-driver-type", &drv_type) == 0) {
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index 41164748723d..771fb5d18585 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -225,6 +225,23 @@ static int sdio_read_cccr(struct mmc_card *card, u32 ocr)
card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_C;
if (data & SDIO_DRIVE_SDTD)
card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_D;
+
+ if (card->host->caps2 & MMC_CAP2_SDIO_ASYNC_IRQ) {
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTERRUPT_EXT, 0,
+ &data);
+ if (ret)
+ goto out;
+
+ if (data & SDIO_INTERRUPT_EXT_SAI) {
+ data |= SDIO_INTERRUPT_EXT_EAI;
+ ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_INTERRUPT_EXT,
+ data, NULL);
+ if (ret)
+ goto out;
+
+ card->cccr.enable_async_irq = 1;
+ }
+ }
}
/* if no uhs mode ensure we check for high speed */
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 37f975875102..4df9182bc0e6 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -219,7 +219,8 @@ struct sdio_cccr {
wide_bus:1,
high_power:1,
high_speed:1,
- disable_cd:1;
+ disable_cd:1,
+ enable_async_irq:1;
};
struct sdio_cis {
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 7afb57cab00b..502a5418264c 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -402,6 +402,7 @@ struct mmc_host {
#define MMC_CAP2_CRYPTO 0
#endif
#define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */
+#define MMC_CAP2_SDIO_ASYNC_IRQ (1 << 29) /* SDIO host supports asynchronous interrupt */
int fixed_drv_type; /* fixed driver type for non-removable media */
diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h
index 2a05d1ac4f0e..1ef400f28642 100644
--- a/include/linux/mmc/sdio.h
+++ b/include/linux/mmc/sdio.h
@@ -159,6 +159,11 @@
#define SDIO_DTSx_SET_TYPE_A (1 << SDIO_DRIVE_DTSx_SHIFT)
#define SDIO_DTSx_SET_TYPE_C (2 << SDIO_DRIVE_DTSx_SHIFT)
#define SDIO_DTSx_SET_TYPE_D (3 << SDIO_DRIVE_DTSx_SHIFT)
+
+#define SDIO_CCCR_INTERRUPT_EXT 0x16
+#define SDIO_INTERRUPT_EXT_SAI (1 << 0)
+#define SDIO_INTERRUPT_EXT_EAI (1 << 1)
+
/*
* Function Basic Registers (FBR)
*/
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v3 2/3] dt-bindings: pwm: Document clk based PWM controller
From: Krzysztof Kozlowski @ 2022-01-21 7:34 UTC (permalink / raw)
To: Nikita Travkin
Cc: thierry.reding, lee.jones, u.kleine-koenig, robh+dt, sboyd,
linus.walleij, masneyb, linux-pwm, devicetree,
linux-kernel@vger.kernel.org, ~postmarketos/upstreaming
In-Reply-To: <20220120161442.140800-3-nikita@trvn.ru>
On Thu, 20 Jan 2022 at 17:15, Nikita Travkin <nikita@trvn.ru> wrote:
>
> Add YAML devicetree binding for clk based PWM controller
>
> Signed-off-by: Nikita Travkin <nikita@trvn.ru>
> --
> Changes in v2:
> - fix the file name.
> ---
> .../devicetree/bindings/pwm/clk-pwm.yaml | 45 +++++++++++++++++++
> 1 file changed, 45 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/clk-pwm.yaml
>
> diff --git a/Documentation/devicetree/bindings/pwm/clk-pwm.yaml b/Documentation/devicetree/bindings/pwm/clk-pwm.yaml
> new file mode 100644
> index 000000000000..4fb2c1baaad4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/clk-pwm.yaml
> @@ -0,0 +1,45 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/clk-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Clock based PWM controller
> +
> +maintainers:
> + - Nikita Travkin <nikita@trvn.ru>
> +
> +description: |
> + Some systems have clocks that can be exposed to external devices.
> + (e.g. by muxing them to GPIO pins)
> + It's often possible to control duty-cycle of such clocks which makes them
> + suitable for generating PWM signal.
> +
> +allOf:
> + - $ref: pwm.yaml#
> +
> +properties:
> + compatible:
> + const: clk-pwm
> +
> + clocks:
> + description: Clock used to generate the signal.
> + maxItems: 1
> +
> + "#pwm-cells":
> + const: 2
> +
> +unevaluatedProperties: false
> +
> +required:
> + - clocks
> +
> +examples:
> + - |
> + pwm-flash {
Node names should be generic (see devicetree specification), so just "pwm".
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 35/35] brcmfmac: common: Add support for external calibration blobs
From: Arend van Spriel @ 2022-01-21 7:35 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-36-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 898 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> The calibration blob for a chip is normally stored in SROM and loaded
> internally by the firmware. However, Apple ARM64 platforms instead store
> it as part of platform configuration data, and provide it via the Apple
> Device Tree. We forward this into the Linux DT in the bootloader.
>
> Add support for taking this blob from the DT and loading it into the
> dongle. The loading mechanism is the same as used for the CLM and TxCap
> blobs.
>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../broadcom/brcm80211/brcmfmac/common.c | 24 +++++++++++++++++++
> .../broadcom/brcm80211/brcmfmac/common.h | 2 ++
> .../wireless/broadcom/brcm80211/brcmfmac/of.c | 8 +++++++
> 3 files changed, 34 insertions(+)
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 24/35] brcmfmac: feature: Add support for setting feats based on WLC version
From: Arend van Spriel @ 2022-01-21 7:35 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-25-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 927 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> The "wlc_ver" iovar returns information on the WLC and EPI versions.
> This can be used to determine whether the PMKID_V2 and _V3 features are
> supported.
I have my doubts whether this mechanism will be reliable, but we can
wait and see whatever hits the fan over time. I stayed away from this
because it is not well guarded. Especially when there are 4 entities
these days spinning firmware and tinkering on the firmware api without
much collaboration.
Acked-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../broadcom/brcm80211/brcmfmac/feature.c | 48 +++++++++++++++++++
> .../broadcom/brcm80211/brcmfmac/feature.h | 4 +-
> .../broadcom/brcm80211/brcmfmac/fwil_types.h | 25 ++++++++++
> 3 files changed, 76 insertions(+), 1 deletion(-)
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 25/35] brcmfmac: cfg80211: Add support for PMKID_V3 operations
From: Arend van Spriel @ 2022-01-21 7:35 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-26-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 672 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> Add support for the new PMKID_V3 API, which allows performing PMKID
> mutations individually, instead of requiring the driver to keep track of
> the full list. This new API is required by at least BCM4387.
>
> Note that PMKID_V2 is not implemented yet.
>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../broadcom/brcm80211/brcmfmac/cfg80211.c | 52 +++++++++++-
> .../broadcom/brcm80211/brcmfmac/fwil_types.h | 83 +++++++++++++++++++
> 2 files changed, 132 insertions(+), 3 deletions(-)
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 26/35] brcmfmac: cfg80211: Pass the PMK in binary instead of hex
From: Arend van Spriel @ 2022-01-21 7:35 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-27-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 766 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> Apparently the hex passphrase mechanism does not work on newer
> chips/firmware (e.g. BCM4387). It seems there was a simple way of
> passing it in binary all along, so use that and avoid the hexification.
>
> OpenBSD has been doing it like this from the beginning, so this should
> work on all chips.
>
> Also clear the structure before setting the PMK. This was leaking
> uninitialized stack contents to the device.
>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 13 +++++++------
> 1 file changed, 7 insertions(+), 6 deletions(-)
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 27/35] brcmfmac: pcie: Add IDs/properties for BCM4387
From: Arend van Spriel @ 2022-01-21 7:35 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-28-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 2170 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> This chip is present on Apple M1 Pro/Max (t600x) platforms:
>
> * maldives (apple,j314s): MacBook Pro (14-inch, M1 Pro, 2021)
> * maldives (apple,j314c): MacBook Pro (14-inch, M1 Max, 2021)
> * madagascar (apple,j316s): MacBook Pro (16-inch, M1 Pro, 2021)
> * madagascar (apple,j316c): MacBook Pro (16-inch, M1 Max, 2021)
>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>>
Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c | 2 ++
> drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 8 ++++++++
> .../net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h | 2 ++
> 3 files changed, 12 insertions(+)
[...]
> diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
> index e4f2aff3c0d5..0d76440ec228 100644
> --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
> +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
> @@ -63,6 +63,7 @@ BRCMF_FW_DEF(4366C, "brcmfmac4366c-pcie");
> BRCMF_FW_DEF(4371, "brcmfmac4371-pcie");
> BRCMF_FW_CLM_DEF(4377B3, "brcmfmac4377b3-pcie");
> BRCMF_FW_CLM_DEF(4378B1, "brcmfmac4378b1-pcie");
> +BRCMF_FW_CLM_DEF(4387C2, "brcmfmac4387c2-pcie");
>
> /* firmware config files */
> MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.txt");
> @@ -96,6 +97,7 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
> BRCMF_FW_ENTRY(BRCM_CC_4371_CHIP_ID, 0xFFFFFFFF, 4371),
> BRCMF_FW_ENTRY(BRCM_CC_4377_CHIP_ID, 0xFFFFFFFF, 4377B3), /* 4 */
> BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0xFFFFFFFF, 4378B1), /* 3 */
> + BRCMF_FW_ENTRY(BRCM_CC_4387_CHIP_ID, 0xFFFFFFFF, 4387C2), /* 7 */
Regarding the revmask in this firmware mapping table my common practice
was to disable older revisions and enable for given revision and newer
until proven otherwise. So for the 4387c2 that would have to following
mask 0xFFFFFF80 (if rev 7 indeed matches with c2).
> };
>
> #define BRCMF_PCIE_FW_UP_TIMEOUT 5000 /* msec */
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 34/35] brcmfmac: pcie: Load and provide TxCap blobs
From: Arend van Spriel @ 2022-01-21 7:36 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-35-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 486 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> These blobs are named .txcap_blob, and exist alongside the existing
> .clm_blob files. Use the existing firmware machinery to provide them to
> the core.
>
> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 13 +++++++++++++
> 1 file changed, 13 insertions(+)
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* Re: [PATCH v2 33/35] brcmfmac: common: Add support for downloading TxCap blobs
From: Arend van Spriel @ 2022-01-21 7:36 UTC (permalink / raw)
To: Hector Martin, Kalle Valo, David S. Miller, Jakub Kicinski,
Rob Herring, Rafael J. Wysocki, Len Brown, Arend van Spriel,
Franky Lin, Hante Meuleman, Chi-hsien Lin, Wright Feng,
Dmitry Osipenko
Cc: Sven Peter, Alyssa Rosenzweig, Mark Kettenis,
Rafał Miłecki, Pieter-Paul Giesberts, Linus Walleij,
Hans de Goede, John W. Linville, brian m. carlson,
Andy Shevchenko, linux-wireless, netdev, devicetree, linux-kernel,
linux-acpi, brcm80211-dev-list.pdl, SHA-cyfmac-dev-list
In-Reply-To: <20220104072658.69756-34-marcan@marcan.st>
[-- Attachment #1: Type: text/plain, Size: 2299 bytes --]
On 1/4/2022 8:26 AM, Hector Martin wrote:
> The TxCap blobs are additional data blobs used on Apple devices, and
> are uploaded analogously to CLM blobs. Add core support for doing this.
Reviewed-by: Arend van Spriel <arend.vanspriel@broadcom.com>
> Acked-by: Linus Walleij <linus.walleij@linaro.org>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> ---
> .../broadcom/brcm80211/brcmfmac/bus.h | 1 +
> .../broadcom/brcm80211/brcmfmac/common.c | 97 +++++++++++++------
> 2 files changed, 71 insertions(+), 27 deletions(-)
>
> diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
> index b13af8f631f3..f4bd98da9761 100644
> --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
> +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
> @@ -39,6 +39,7 @@ enum brcmf_bus_protocol_type {
> /* Firmware blobs that may be available */
> enum brcmf_blob_type {
> BRCMF_BLOB_CLM,
> + BRCMF_BLOB_TXCAP,
> };
>
> struct brcmf_mp_device;
> diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
> index c84c48e49fde..d65308c3f070 100644
> --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
> +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
[...]
> @@ -165,20 +157,64 @@ static int brcmf_c_process_clm_blob(struct brcmf_if *ifp)
> } while ((datalen > 0) && (err == 0));
>
[...]
> +static int brcmf_c_process_txcap_blob(struct brcmf_if *ifp)
> +{
> + struct brcmf_pub *drvr = ifp->drvr;
> + struct brcmf_bus *bus = drvr->bus_if;
> + const struct firmware *fw = NULL;
> + s32 err;
> +
> + brcmf_dbg(TRACE, "Enter\n");
> +
> + err = brcmf_bus_get_blob(bus, &fw, BRCMF_BLOB_TXCAP);
> + if (err || !fw) {
> + brcmf_info("no txcap_blob available (err=%d)\n", err);
> + return 0;
> + }
> +
> + brcmf_info("TxCap blob found, loading\n");
> + err = brcmf_c_download_blob(ifp, fw->data, fw->size,
> + "txcapload", "txcapload_status");
Although unlikely that we end up here with a firmware that does not
support this command it is not impossible. Should we handle that here or
introduce a feature flag for txcap loading?
> + release_firmware(fw);
> return err;
> }
[-- Attachment #2: S/MIME Cryptographic Signature --]
[-- Type: application/pkcs7-signature, Size: 4219 bytes --]
^ permalink raw reply
* [PATCH v10 0/4] clk: meson: add a sub EMMC clock controller support
From: Liang Yang @ 2022-01-21 7:45 UTC (permalink / raw)
To: Neil Armstrong, Jerome Brunet, Kevin Hilman, Michael Turquette,
Stephen Boyd, Rob Herring, linux-clk
Cc: Liang Yang, Martin Blumenstingl, Jianxin Pan, Victor Wan,
XianWei Zhao, Kelvin Zhang, BiChao Zheng, YongHui Yu,
linux-arm-kernel, linux-amlogic, linux-kernel, devicetree
This driver will add a MMC clock controller driver support.
The original idea about adding a clock controller is during the
discussion in the NAND driver mainline effort[1].
This driver is tested in the S400 board (AXG platform) with NAND driver.
Changes since v9 [10]
- use clk_parent_data instead of parent_names
Changes since v8 [9]
- use MESON_SCLK_ONE_BASED instead of CLK_DIVIDER_ONE_BASED
- use struct_size to caculate onecell_data
- add clk-phase-delay.h
- define CLK_DELAY_STEP_PS_GX and CLK_DELAY_STEP_PS_AXG
Changes since v7 [8]
- move meson_clk_get_phase_delay_data() from header to driver
- CONFIG sclk-div with COMMON_CLK_AMLOGIC instead of COMMON_CLK_AMLOGIC_AUDIO
- remove onecell date and ID for internal MUX clk
- use helper for functions for ONE_BASED in sclk-div
- add ONE_BASED support for duty cycle
Changes since v6 [7]:
- add one based support for sclk divier
- alloc sclk in probe for multiple instance
- fix coding styles
Changes since v5 [6]:
- remove divider ops with .init and use sclk_div instead
- drop CLK_DIVIDER_ROUND_CLOSEST in mux and div
- drop the useless type cast
Changes since v4 [5]:
- use struct parm in phase delay driver
- remove 0 delay releted part in phase delay driver
- don't rebuild the parent name once again
- add divider ops with .init
Changes since v3 [4]:
- separate clk-phase-delay driver
- replace clk_get_rate() with clk_hw_get_rate()
- collect Rob's R-Y
- drop 'meson-' prefix from compatible string
Changes since v2 [3]:
- squash dt-binding clock-id patch
- update license
- fix alignment
- construct a clk register helper() function
Changes since v1 [2]:
- implement phase clock
- update compatible name
- adjust file name
- divider probe() into small functions, and re-use them
[1] https://lkml.kernel.org/r/20180628090034.0637a062@xps13
[2] https://lkml.kernel.org/r/20180703145716.31860-1-yixun.lan@amlogic.com
[3] https://lkml.kernel.org/r/20180710163658.6175-1-yixun.lan@amlogic.com
[4] https://lkml.kernel.org/r/20180712211244.11428-1-yixun.lan@amlogic.com
[5] https://lkml.kernel.org/r/20180809070724.11935-4-yixun.lan@amlogic.com
[6] https://lkml.kernel.org/r/1539839245-13793-1-git-send-email-jianxin.pan@amlogic.com
[7] https://lkml.kernel.org/r/1541089855-19356-1-git-send-email-jianxin.pan@amlogic.com
[8] https://lkml.kernel.org/r/1544457877-51301-1-git-send-email-jianxin.pan@amlogic.com
[9] https://lkml.kernel.org/r/1545063850-21504-1-git-send-email-jianxin.pan@amlogic.com
[10] https://lore.kernel.org/all/20220113115745.45826-1-liang.yang@amlogic.com/
Liang Yang (4):
clk: meson: add one based divider support for sclk
clk: meson: add emmc sub clock phase delay driver
clk: meson: add DT documentation for emmc clock controller
clk: meson: add sub MMC clock controller driver
.../bindings/clock/amlogic,mmc-clkc.yaml | 64 ++++
drivers/clk/meson/Kconfig | 18 ++
drivers/clk/meson/Makefile | 2 +
drivers/clk/meson/clk-phase-delay.c | 69 ++++
drivers/clk/meson/clk-phase-delay.h | 20 ++
drivers/clk/meson/mmc-clkc.c | 302 ++++++++++++++++++
drivers/clk/meson/sclk-div.c | 59 ++--
drivers/clk/meson/sclk-div.h | 3 +
include/dt-bindings/clock/amlogic,mmc-clkc.h | 14 +
9 files changed, 529 insertions(+), 22 deletions(-)
create mode 100644 Documentation/devicetree/bindings/clock/amlogic,mmc-clkc.yaml
create mode 100644 drivers/clk/meson/clk-phase-delay.c
create mode 100644 drivers/clk/meson/clk-phase-delay.h
create mode 100644 drivers/clk/meson/mmc-clkc.c
create mode 100644 include/dt-bindings/clock/amlogic,mmc-clkc.h
--
2.34.1
^ permalink raw reply
* [PATCH v10 1/4] clk: meson: add one based divider support for sclk
From: Liang Yang @ 2022-01-21 7:45 UTC (permalink / raw)
To: Neil Armstrong, Jerome Brunet, Kevin Hilman, Michael Turquette,
Stephen Boyd, Rob Herring, linux-clk
Cc: Liang Yang, Martin Blumenstingl, Jianxin Pan, Victor Wan,
XianWei Zhao, Kelvin Zhang, BiChao Zheng, YongHui Yu,
linux-arm-kernel, linux-amlogic, linux-kernel, devicetree
In-Reply-To: <20220121074508.42168-1-liang.yang@amlogic.com>
When MESON_SCLK_ONE_BASED flag is set, the sclk divider will be:
one based divider (div = val), and zero value gates the clock
Signed-off-by: Liang Yang <liang.yang@amlogic.com>
---
drivers/clk/meson/sclk-div.c | 59 ++++++++++++++++++++++--------------
drivers/clk/meson/sclk-div.h | 3 ++
2 files changed, 40 insertions(+), 22 deletions(-)
diff --git a/drivers/clk/meson/sclk-div.c b/drivers/clk/meson/sclk-div.c
index 76d31c0a3342..4ddc1763a12d 100644
--- a/drivers/clk/meson/sclk-div.c
+++ b/drivers/clk/meson/sclk-div.c
@@ -4,16 +4,17 @@
* Author: Jerome Brunet <jbrunet@baylibre.com>
*
* Sample clock generator divider:
- * This HW divider gates with value 0 but is otherwise a zero based divider:
+ * This HW divider gates with value 0:
*
* val >= 1
- * divider = val + 1
+ * divider = val + 1 if ONE_BASED is not set, otherwise divider = val.
*
* The duty cycle may also be set for the LR clock variant. The duty cycle
* ratio is:
*
* hi = [0 - val]
- * duty_cycle = (1 + hi) / (1 + val)
+ * duty_cycle = (1 + hi) / (1 + val) if ONE_BASED is not set, otherwise:
+ * duty_cycle = hi / (1 + val)
*/
#include <linux/clk-provider.h>
@@ -28,22 +29,37 @@ meson_sclk_div_data(struct clk_regmap *clk)
return (struct meson_sclk_div_data *)clk->data;
}
-static int sclk_div_maxval(struct meson_sclk_div_data *sclk)
+static inline int sclk_get_reg(int val, unsigned char flag)
{
- return (1 << sclk->div.width) - 1;
+ if ((flag & MESON_SCLK_ONE_BASED) || !val)
+ return val;
+ return val - 1;
+}
+
+static inline int sclk_get_divider(int reg, unsigned char flag)
+{
+ if (flag & MESON_SCLK_ONE_BASED)
+ return reg;
+ return reg + 1;
}
static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk)
{
- return sclk_div_maxval(sclk) + 1;
+ unsigned int reg = (1 << sclk->div.width) - 1;
+
+ return sclk_get_divider(reg, sclk->flags);
}
static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate,
- unsigned long prate, int maxdiv)
+ unsigned long prate)
{
int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate);
+ struct clk_regmap *clk = to_clk_regmap(hw);
+ struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+ int mindiv = sclk_get_divider(1, sclk->flags);
+ int maxdiv = sclk_div_maxdiv(sclk);
- return clamp(div, 2, maxdiv);
+ return clamp(div, mindiv, maxdiv);
}
static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
@@ -51,25 +67,25 @@ static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
struct meson_sclk_div_data *sclk)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
- int bestdiv = 0, i;
+ int bestdiv = 0, i, mindiv;
unsigned long maxdiv, now, parent_now;
unsigned long best = 0, best_parent = 0;
if (!rate)
rate = 1;
- maxdiv = sclk_div_maxdiv(sclk);
-
if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT))
- return sclk_div_getdiv(hw, rate, *prate, maxdiv);
+ return sclk_div_getdiv(hw, rate, *prate);
/*
* The maximum divider we can use without overflowing
* unsigned long in rate * i below
*/
+ maxdiv = sclk_div_maxdiv(sclk);
maxdiv = min(ULONG_MAX / rate, maxdiv);
+ mindiv = sclk_get_divider(1, sclk->flags);
- for (i = 2; i <= maxdiv; i++) {
+ for (i = mindiv; i <= maxdiv; i++) {
/*
* It's the most ideal case if the requested rate can be
* divided from parent clock without needing to change
@@ -115,10 +131,7 @@ static void sclk_apply_ratio(struct clk_regmap *clk,
sclk->cached_duty.num,
sclk->cached_duty.den);
- if (hi)
- hi -= 1;
-
- meson_parm_write(clk->map, &sclk->hi, hi);
+ meson_parm_write(clk->map, &sclk->hi, sclk_get_reg(hi, sclk->flags));
}
static int sclk_div_set_duty_cycle(struct clk_hw *hw,
@@ -149,7 +162,7 @@ static int sclk_div_get_duty_cycle(struct clk_hw *hw,
}
hi = meson_parm_read(clk->map, &sclk->hi);
- duty->num = hi + 1;
+ duty->num = sclk_get_divider(hi, sclk->flags);
duty->den = sclk->cached_div;
return 0;
}
@@ -157,10 +170,13 @@ static int sclk_div_get_duty_cycle(struct clk_hw *hw,
static void sclk_apply_divider(struct clk_regmap *clk,
struct meson_sclk_div_data *sclk)
{
+ unsigned int div;
+
if (MESON_PARM_APPLICABLE(&sclk->hi))
sclk_apply_ratio(clk, sclk);
- meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1);
+ div = sclk_get_reg(sclk->cached_div, sclk->flags);
+ meson_parm_write(clk->map, &sclk->div, div);
}
static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -168,9 +184,8 @@ static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
- unsigned long maxdiv = sclk_div_maxdiv(sclk);
- sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv);
+ sclk->cached_div = sclk_div_getdiv(hw, rate, prate);
if (clk_hw_is_enabled(hw))
sclk_apply_divider(clk, sclk);
@@ -228,7 +243,7 @@ static int sclk_div_init(struct clk_hw *hw)
if (!val)
sclk->cached_div = sclk_div_maxdiv(sclk);
else
- sclk->cached_div = val + 1;
+ sclk->cached_div = sclk_get_divider(val, sclk->flags);
sclk_div_get_duty_cycle(hw, &sclk->cached_duty);
diff --git a/drivers/clk/meson/sclk-div.h b/drivers/clk/meson/sclk-div.h
index b64b2a32005f..944dab5ec0cf 100644
--- a/drivers/clk/meson/sclk-div.h
+++ b/drivers/clk/meson/sclk-div.h
@@ -10,11 +10,14 @@
#include <linux/clk-provider.h>
#include "parm.h"
+#define MESON_SCLK_ONE_BASED BIT(0)
+
struct meson_sclk_div_data {
struct parm div;
struct parm hi;
unsigned int cached_div;
struct clk_duty cached_duty;
+ u8 flags;
};
extern const struct clk_ops meson_sclk_div_ops;
--
2.34.1
^ permalink raw reply related
* [PATCH v4 0/2] Add I2C control driver for Sunplus SP7021 SoC
From: Li-hao Kuo @ 2022-01-21 7:50 UTC (permalink / raw)
To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig, robh+dt,
linux-kernel, linux-i2c, devicetree
Cc: lh.kuo, wells.lu, Li-hao Kuo
This is a patch series for I2C driver for Sunplus SP7021 SoC.
Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control.
Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html
Li-hao Kuo (2):
i2c: Add i2c driver for Sunplus SP7021
dt-bindings:i2c: Add Sunplus SP7021 schema
.../devicetree/bindings/i2c/i2c-sunplus.yaml | 73 +
MAINTAINERS | 7 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-sunplus.c | 1447 ++++++++++++++++++++
5 files changed, 1538 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml
create mode 100644 drivers/i2c/busses/i2c-sunplus.c
--
2.7.4
^ permalink raw reply
* [PATCH v4 2/2] dt-bindings:i2c: Add Sunplus SP7021 schema
From: Li-hao Kuo @ 2022-01-21 7:50 UTC (permalink / raw)
To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig, robh+dt,
linux-kernel, linux-i2c, devicetree
Cc: lh.kuo, wells.lu, Li-hao Kuo
In-Reply-To: <cover.1642751147.git.lhjeff911@gmail.com>
Add bindings for Sunplus SP7021 i2c driver
Signed-off-by: Li-hao Kuo <lhjeff911@gmail.com>
---
Changes in v4:
- Modified the YAML file : fix indentation issue
.../devicetree/bindings/i2c/i2c-sunplus.yaml | 73 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 74 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml
diff --git a/Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml b/Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml
new file mode 100644
index 0000000..e44a7ff
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml
@@ -0,0 +1,73 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/i2c-sunplus.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus I2C controller
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml#
+
+maintainers:
+ - Li-hao Kuo <lhjeff911@gmail.com>
+
+properties:
+ compatible:
+ enum:
+ - sunplus,sp7021-i2cm
+ - sunplus,q645-i2cm
+
+ reg:
+ items:
+ - description: I2C registers
+ - description: I2C DMA registers
+ - description: I2C DMA power registers
+
+ reg-names:
+ items:
+ - const: i2cm
+ - const: i2cmdma
+ - const: i2cdmapower
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - clocks
+ - resets
+ - clock-frequency
+ - pinctrl-names
+ - pinctrl-0
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/sp-sp7021.h>
+ #include <dt-bindings/reset/sp-sp7021.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c@9C004600 {
+ compatible = "sunplus,sp7021-i2cm";
+ reg = <0x9c004600 0x80>, <0x9c004680 0x80>, <0x9c000000 0x80>;
+ reg-names = "i2cm", "i2cmdma", "i2cdmapower";
+ interrupt-parent = <&intc>;
+ interrupts = <174 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clkc I2CM0>;
+ resets = <&rstc RST_I2CM0>;
+ clock-frequency = <100000>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&i2cm0_pins>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 4f83935..44eddba 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18494,6 +18494,7 @@ SUNPLUS I2C CONTROLLER INTERFACE DRIVER
M: Li-hao Kuo <lhjeff911@gmail.com>
L: linux-i2c@vger.kernel.org
S: Maintained
+F: Documentation/devicetree/bindings/i2c/i2c-sunplus.yaml
F: drivers/i2c/busses/i2c-sunplus.c
SUPERH
--
2.7.4
^ permalink raw reply related
* [PATCH v4 1/2] i2c: Add i2c driver for Sunplus SP7021
From: Li-hao Kuo @ 2022-01-21 7:50 UTC (permalink / raw)
To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig, robh+dt,
linux-kernel, linux-i2c, devicetree
Cc: lh.kuo, wells.lu, Li-hao Kuo
In-Reply-To: <cover.1642751147.git.lhjeff911@gmail.com>
Add i2c driver for Sunplus SP7021.
Signed-off-by: Li-hao Kuo <lhjeff911@gmail.com>
---
Changes in v4:
- Modified the YAML file : fix indentation issue
MAINTAINERS | 6 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-sunplus.c | 1447 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1464 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-sunplus.c
diff --git a/MAINTAINERS b/MAINTAINERS
index b84e2d5..4f83935 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18490,6 +18490,12 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/dlink/sundance.c
+SUNPLUS I2C CONTROLLER INTERFACE DRIVER
+M: Li-hao Kuo <lhjeff911@gmail.com>
+L: linux-i2c@vger.kernel.org
+S: Maintained
+F: drivers/i2c/busses/i2c-sunplus.c
+
SUPERH
M: Yoshinori Sato <ysato@users.sourceforge.jp>
M: Rich Felker <dalias@libc.org>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 42da31c..df35b0e 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1055,6 +1055,16 @@ config I2C_SUN6I_P2WI
This interface is used to connect to specific PMIC devices (like the
AXP221).
+config I2C_SUNPLUS
+ tristate "Sunplus I2C driver"
+ depends on SOC_SP7021 || COMPILE_TEST
+ help
+ Say Y here to include support for I2C controller in the
+ Sunplus SoCs.
+
+ This driver can also be built as a module. If so, the module will be
+ called as i2c-sunplus.
+
config I2C_SYNQUACER
tristate "Socionext SynQuacer I2C controller"
depends on ARCH_SYNQUACER || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 1d00dce..00f2e9a 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -107,6 +107,7 @@ obj-$(CONFIG_I2C_STM32F4) += i2c-stm32f4.o
i2c-stm32f7-drv-objs := i2c-stm32f7.o i2c-stm32.o
obj-$(CONFIG_I2C_STM32F7) += i2c-stm32f7-drv.o
obj-$(CONFIG_I2C_SUN6I_P2WI) += i2c-sun6i-p2wi.o
+obj-$(CONFIG_I2C_SUNPLUS) += i2c-sunplus.o
obj-$(CONFIG_I2C_SYNQUACER) += i2c-synquacer.o
obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o
obj-$(CONFIG_I2C_TEGRA_BPMP) += i2c-tegra-bpmp.o
diff --git a/drivers/i2c/busses/i2c-sunplus.c b/drivers/i2c/busses/i2c-sunplus.c
new file mode 100644
index 0000000..e78ec15
--- /dev/null
+++ b/drivers/i2c/busses/i2c-sunplus.c
@@ -0,0 +1,1447 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Sunplus Inc.
+ * Author: Li-hao Kuo <lhjeff911@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#define SP_I2C_STD_FREQ 100
+#define SP_I2C_FAST_FREQ 400
+#define SP_I2C_SLEEP_TIMEOUT 200
+#define SP_I2C_SCL_DELAY 1
+#define SP_CLK_SOURCE_FREQ 27000
+#define SP_BUFFER_SIZE 1024
+#define SP_I2C_EMP_THOLD 4
+
+#define SP_I2C_BURST_RDATA_BYTES 16
+#define SP_I2C_BURST_RDATA_FLAG (BIT(31) | BIT(15))
+#define SP_I2C_BURST_RDATA_ALL_FLAG GENMASK(31, 0)
+
+#define SP_I2C_CTL0_REG 0x0000
+//control0
+#define SP_I2C_CTL0_FREQ_MASK GENMASK(26, 24)
+#define SP_I2C_CTL0_PREFETCH BIT(18)
+#define SP_I2C_CTL0_RESTART_EN BIT(17)
+#define SP_I2C_CTL0_SUBADDR_EN BIT(16)
+#define SP_I2C_CTL0_SW_RESET BIT(15)
+#define SP_I2C_CTL0_SLAVE_ADDR_MASK GENMASK(7, 1)
+
+#define SP_I2C_CTL1_REG 0x0004
+//control1
+#define SP_I2C_CTL1_ALL_CLR GENMASK(9, 0)
+#define SP_I2C_CTL1_EMP_CLR BIT(9)
+#define SP_I2C_CTL1_SCL_HOLD_TOO_LONG_CLR BIT(8)
+#define SP_I2C_CTL1_SCL_WAIT_CLR BIT(7)
+#define SP_I2C_CTL1_EMP_THOLD_CLR BIT(6)
+#define SP_I2C_CTL1_DATA_NACK_CLR BIT(5)
+#define SP_I2C_CTL1_ADDRESS_NACK_CLR BIT(4)
+#define SP_I2C_CTL1_BUSY_CLR BIT(3)
+#define SP_I2C_CTL1_CLKERR_CLR BIT(2)
+#define SP_I2C_CTL1_DONE_CLR BIT(1)
+#define SPI2C_CTL1_SIFBUSY_CLR BIT(0)
+
+#define SP_I2C_CTL2_REG 0x0008
+//control2
+#define SP_I2C_CTL2_FREQ_CUSTOM_MASK GENMASK(10, 0) // 11 bit
+#define SP_I2C_CTL2_SCL_DELAY_MASK GENMASK(25, 24)
+#define SP_I2C_CTL2_SDA_HALF_ENABLE BIT(31)
+
+#define SP_I2C_INT_REG 0x001c
+#define SP_I2C_INT_EN0_REG 0x0020
+#define SP_I2C_MOD_REG 0x0024
+#define SP_I2C_CTL6_REG 0x0030
+#define SP_I2C_INT_EN1_REG 0x0034
+#define SP_I2C_STATUS3_REG 0x0038
+#define SP_I2C_STATUS4_REG 0x0040
+#define SP_I2C_INT_EN2_REG 0x0040
+#define SP_I2C_CTL7_REG 0x0044
+//control7
+#define SP_I2C_CTL7_RD_MASK GENMASK(31, 16)
+#define SP_I2C_CTL7_WR_MASK GENMASK(15, 0) //bit[31:16]
+//i2c master mode
+#define SP_I2C_MODE_DMA_MODE BIT(2)
+#define SP_I2C_MODE_MANUAL_MODE BIT(1)
+#define SP_I2C_MODE_MANUAL_TRIG BIT(0)
+
+#define SP_I2C_DATA0_REG 0x0060
+
+#define SP_I2C_DMA_CONF_REG 0x0004
+#define SP_I2C_DMA_LEN_REG 0x0008
+#define SP_I2C_DMA_ADDR_REG 0x000c
+//dma config
+#define SP_I2C_DMA_CFG_DMA_GO BIT(8)
+#define SP_I2C_DMA_CFG_NON_BUF_MODE BIT(2)
+#define SP_I2C_DMA_CFG_SAME_SLAVE BIT(1)
+#define SP_I2C_DMA_CFG_DMA_MODE BIT(0)
+
+#define SP_I2C_DMA_FLAG_REG 0x0014
+//dma interrupt flag
+#define SP_I2C_DMA_INT_LENGTH0_FLAG BIT(6)
+#define SP_I2C_DMA_INT_THRESHOLD_FLAG BIT(5)
+#define SP_I2C_DMA_INT_IP_TIMEOUT_FLAG BIT(4)
+#define SP_I2C_DMA_INT_GDMA_TIMEOUT_FLAG BIT(3)
+#define SP_I2C_DMA_INT_WB_EN_ERROR_FLAG BIT(2)
+#define SP_I2C_DMA_INT_WCNT_ERROR_FLAG BIT(1)
+#define SP_I2C_DMA_INT_DMA_DONE_FLAG BIT(0)
+
+#define SP_I2C_DMA_INT_EN_REG 0x0018
+//dma interrupt enable
+#define SP_I2C_DMA_EN_LENGTH0_INT BIT(6)
+#define SP_I2C_DMA_EN_THRESHOLD_INT BIT(5)
+#define SP_I2C_DMA_EN_IP_TIMEOUT_INT BIT(4)
+#define SP_I2C_DMA_EN_GDMA_TIMEOUT_INT BIT(3)
+#define SP_I2C_DMA_EN_WB_EN_ERROR_INT BIT(2)
+#define SP_I2C_DMA_EN_WCNT_ERROR_INT BIT(1)
+#define SP_I2C_DMA_EN_DMA_DONE_INT BIT(0)
+//interrupt
+#define SP_I2C_INT_RINC_INDEX GENMASK(20, 18) //bit[20:18]
+#define SP_I2C_INT_WINC_INDEX GENMASK(17, 15) //bit[17:15]
+#define SP_I2C_INT_SCL_HOLD_TOO_LONG_FLAG BIT(11)
+#define SP_I2C_INT_WFIFO_ENABLE BIT(10)
+#define SP_I2C_INT_FULL_FLAG BIT(9)
+#define SP_I2C_INT_EMPTY_FLAG BIT(8)
+#define SP_I2C_INT_SCL_WAIT_FLAG BIT(7)
+#define SP_I2C_INT_EMPTY_THRESHOLD_FLAG BIT(6)
+#define SP_I2C_INT_DATA_NACK_FLAG BIT(5)
+#define SP_I2C_INT_ADDRESS_NACK_FLAG BIT(4)
+#define SP_I2C_INT_BUSY_FLAG BIT(3)
+#define SP_I2C_INT_CLKERR_FLAG BIT(2)
+#define SP_I2C_INT_DONE_FLAG BIT(1)
+#define SP_I2C_INT_SIFBUSY_FLAG BIT(0)
+//interrupt enable0
+#define SP_I2C_EN0_SCL_HOLD_TOO_LONG_INT BIT(13)
+#define SP_I2C_EN0_NACK_INT BIT(12)
+#define SP_I2C_EN0_CTL_EMP_THOLD GENMASK(11, 9)
+#define SP_I2C_EN0_EMPTY_INT BIT(8)
+#define SP_I2C_EN0_SCL_WAIT_INT BIT(7)
+#define SP_I2C_EN0_EMP_THOLD_INT BIT(6)
+#define SP_I2C_EN0_DATA_NACK_INT BIT(5)
+#define SP_I2C_EN0_ADDRESS_NACK_INT BIT(4)
+#define SP_I2C_EN0_BUSY_INT BIT(3)
+#define SP_I2C_EN0_CLKERR_INT BIT(2)
+#define SP_I2C_EN0_DONE_INT BIT(1)
+#define SP_I2C_EN0_SIFBUSY_INT BIT(0)
+
+#define SP_I2C_RESET(id, val) (((1 << 16) | (val)) << (id))
+#define SP_I2C_CLKEN(id, val) (((1 << 16) | (val)) << (id))
+#define SP_I2C_GCLKEN(id, val) (((1 << 16) | (val)) << (id))
+
+#define SP_I2C_POWER_CLKEN3 0x0010
+#define SP_I2C_POWER_GCLKEN3 0x0038
+#define SP_I2C_POWER_RESET3 0x0060
+
+enum sp_state_e_ {
+ SPI2C_SUCCESS = 0, /* successful */
+ SPI2C_STATE_WR = 1, /* i2c is write */
+ SPI2C_STATE_RD = 2, /* i2c is read */
+ SPI2C_STATE_IDLE = 3, /* i2c is idle */
+ SPI2C_STATE_DMA_WR = 4,/* i2c is dma write */
+ SPI2C_STATE_DMA_RD = 5, /* i2c is dma read */
+};
+
+enum sp_i2c_switch_e_ {
+ SP_I2C_DMA_POWER_NO_SW = 0,
+ SP_I2C_DMA_POWER_SW = 1,
+};
+
+struct sp_i2c_cmd {
+ unsigned int dev_id;
+ unsigned int freq;
+ unsigned int slave_addr;
+ unsigned int restart_en;
+ unsigned int write_cnt;
+ unsigned int read_cnt;
+ unsigned char *write_data;
+ unsigned char *read_data;
+};
+
+struct sp_i2c_irq_dma_flag {
+ unsigned char dma_done;
+ unsigned char write_cnt_err;
+ unsigned char wb_en_err;
+ unsigned char gdma_timeout;
+ unsigned char ipt_timerout;
+ unsigned char threshold;
+ unsigned char dma_ligth;
+};
+
+struct sp_i2c_irq_flag {
+ unsigned char active_done;
+ unsigned char addr_nack;
+ unsigned char data_nack;
+ unsigned char emp_thold;
+ unsigned char fifo_empty;
+ unsigned char fifo_full;
+ unsigned char scl_hold_too_long;
+ unsigned char read_over_flow;
+};
+
+struct sp_i2c_irq_event {
+ enum sp_state_e_ rw_state;
+ struct sp_i2c_irq_flag irq_flag;
+ struct sp_i2c_irq_dma_flag irq_dma_flag;
+ unsigned int burst_cnt;
+ unsigned int burst_remainder;
+ unsigned int data_index;
+ unsigned int data_total_len;
+ unsigned int reg_data_index;
+ unsigned char busy;
+ unsigned char ret;
+ unsigned char *data_buf;
+};
+
+enum sp_i2c_dma_mode {
+ I2C_DMA_WRITE_MODE,
+ I2C_DMA_READ_MODE,
+};
+
+enum sp_i2c_mode {
+ I2C_WRITE_MODE,
+ I2C_READ_MODE,
+ I2C_RESTART_MODE,
+};
+
+enum sp_i2c_active_mode {
+ I2C_TRIGGER,
+ I2C_AUTO,
+};
+
+struct i2c_compatible {
+ int mode; /* dma power switch*/
+ int total_port;
+};
+
+struct sp_i2c_dev {
+ struct device *dev;
+ struct i2c_adapter adap;
+ struct sp_i2c_cmd spi2c_cmd;
+ struct sp_i2c_irq_event spi2c_irq;
+ struct clk *clk;
+ struct reset_control *rstc;
+ unsigned int mode;
+ unsigned int total_port;
+ unsigned int i2c_clk_freq;
+ int irq;
+ void __iomem *i2c_regs;
+ void __iomem *i2c_dma_regs;
+ void __iomem *i2c_power_regs;
+ wait_queue_head_t wait;
+};
+
+static unsigned int sp_i2cm_get_int_flag(void __iomem *sr)
+{
+ return readl(sr + SP_I2C_INT_REG);
+}
+
+static void sp_i2cm_status_clear(void __iomem *sr, u32 flag)
+{
+ u32 ctl1;
+
+ ctl1 = readl(sr + SP_I2C_CTL1_REG);
+ ctl1 |= flag;
+ writel(ctl1, sr + SP_I2C_CTL1_REG);
+
+ ctl1 = readl(sr + SP_I2C_CTL1_REG);
+ ctl1 &= (~flag);
+ writel(ctl1, sr + SP_I2C_CTL1_REG);
+}
+
+static void sp_i2cm_reset(void __iomem *sr)
+{
+ u32 ctl0;
+
+ ctl0 = readl(sr + SP_I2C_CTL0_REG);
+ ctl0 |= SP_I2C_CTL0_SW_RESET;
+ writel(ctl0, sr + SP_I2C_CTL0_REG);
+
+ udelay(2);
+}
+
+static void sp_i2cm_data0_set(void __iomem *sr, void *wrdata)
+{
+ unsigned int *wdata = wrdata;
+
+ writel(*wdata, sr + SP_I2C_DATA0_REG);
+}
+
+static void sp_i2cm_int_en0_disable(void __iomem *sr, unsigned int int0)
+{
+ u32 val;
+
+ val = readl(sr + SP_I2C_INT_EN0_REG);
+ val &= (~int0);
+ writel(val, sr + SP_I2C_INT_EN0_REG);
+}
+
+static void sp_i2cm_rdata_flag_get(void __iomem *sr, unsigned int *flag)
+{
+ *flag = readl(sr + SP_I2C_STATUS3_REG);
+}
+
+static unsigned int sp_i2cm_over_flag_get(void __iomem *sr)
+{
+ return readl(sr + SP_I2C_STATUS4_REG);
+}
+
+static void sp_i2cm_data_get(void __iomem *sr, unsigned int index, void *rxdata)
+{
+ unsigned int *rdata = rxdata;
+
+ *rdata = readl(sr + SP_I2C_DATA0_REG + (index * 4));
+}
+
+static void sp_i2cm_rdata_flag_clear(void __iomem *sr, unsigned int flag)
+{
+ writel(flag, sr + SP_I2C_CTL6_REG);
+ writel(0, sr + SP_I2C_CTL6_REG);
+}
+
+static void sp_i2cm_clock_freq_set(void __iomem *sr, unsigned int freq)
+{
+ unsigned int div;
+ u32 ctl0, ctl2;
+
+ div = SP_CLK_SOURCE_FREQ / freq;
+ div -= 1;
+ if (SP_CLK_SOURCE_FREQ % freq != 0)
+ div += 1;
+
+ if (div > SP_I2C_CTL2_FREQ_CUSTOM_MASK)
+ div = SP_I2C_CTL2_FREQ_CUSTOM_MASK;
+
+ ctl0 = readl(sr + SP_I2C_CTL0_REG);
+ ctl0 &= ~SP_I2C_CTL0_FREQ_MASK;
+ writel(ctl0, sr + SP_I2C_CTL0_REG);
+
+ ctl2 = readl(sr + SP_I2C_CTL2_REG);
+ ctl2 &= ~SP_I2C_CTL2_FREQ_CUSTOM_MASK;
+ ctl2 |= FIELD_PREP(SP_I2C_CTL2_FREQ_CUSTOM_MASK, div);
+ writel(ctl2, sr + SP_I2C_CTL2_REG);
+}
+
+static void sp_i2cm_slave_addr_set(void __iomem *sr, unsigned int addr)
+{
+ u32 ctl0;
+
+ ctl0 = readl(sr + SP_I2C_CTL0_REG);
+ ctl0 &= ~SP_I2C_CTL0_SLAVE_ADDR_MASK;
+ ctl0 |= FIELD_PREP(SP_I2C_CTL0_SLAVE_ADDR_MASK, addr);
+ writel(ctl0, sr + SP_I2C_CTL0_REG);
+}
+
+static void sp_i2cm_scl_delay_set(void __iomem *sr, unsigned int delay)
+{
+ u32 ctl2;
+
+ ctl2 = readl(sr + SP_I2C_CTL2_REG);
+ ctl2 &= ~SP_I2C_CTL2_SCL_DELAY_MASK;
+ ctl2 |= FIELD_PREP(SP_I2C_CTL2_SCL_DELAY_MASK, delay);
+ ctl2 &= ~SP_I2C_CTL2_SDA_HALF_ENABLE;
+ writel(ctl2, sr + SP_I2C_CTL2_REG);
+}
+
+static void sp_i2cm_trans_cnt_set(void __iomem *sr, unsigned int write_cnt,
+ unsigned int read_cnt)
+{
+ u32 ctl7 = 0;
+
+ ctl7 = FIELD_PREP(SP_I2C_CTL7_RD_MASK, read_cnt) |
+ FIELD_PREP(SP_I2C_CTL7_WR_MASK, write_cnt);
+ writel(ctl7, sr + SP_I2C_CTL7_REG);
+}
+
+static void sp_i2cm_active_mode_set(void __iomem *sr, enum sp_i2c_active_mode mode)
+{
+ u32 val;
+
+ val = readl(sr + SP_I2C_MOD_REG);
+ val &= (~(SP_I2C_MODE_MANUAL_MODE | SP_I2C_MODE_MANUAL_TRIG));
+ switch (mode) {
+ default:
+ case I2C_TRIGGER:
+ break;
+ case I2C_AUTO:
+ val |= SP_I2C_MODE_MANUAL_MODE;
+ break;
+ }
+ writel(val, sr + SP_I2C_MOD_REG);
+}
+
+static void sp_i2cm_data_tx(void __iomem *sr, void *wdata, unsigned int cnt)
+{
+ unsigned int *wrdata = wdata;
+ unsigned int i;
+
+ for (i = 0 ; i < cnt ; i++)
+ writel(wrdata[i], sr + SP_I2C_DATA0_REG + (i * 4));
+}
+
+static void sp_i2cm_rw_mode_set(void __iomem *sr, enum sp_i2c_mode rw_mode)
+{
+ u32 ctl0;
+
+ ctl0 = readl(sr + SP_I2C_CTL0_REG);
+ switch (rw_mode) {
+ default:
+ case I2C_WRITE_MODE:
+ ctl0 &= ~(SP_I2C_CTL0_PREFETCH |
+ SP_I2C_CTL0_RESTART_EN | SP_I2C_CTL0_SUBADDR_EN);
+ break;
+ case I2C_READ_MODE:
+ ctl0 &= (~(SP_I2C_CTL0_RESTART_EN | SP_I2C_CTL0_SUBADDR_EN));
+ ctl0 |= SP_I2C_CTL0_PREFETCH;
+ break;
+ case I2C_RESTART_MODE:
+ ctl0 |= (SP_I2C_CTL0_PREFETCH |
+ SP_I2C_CTL0_RESTART_EN | SP_I2C_CTL0_SUBADDR_EN);
+ break;
+ }
+ writel(ctl0, sr + SP_I2C_CTL0_REG);
+}
+
+static void sp_i2cm_int_en0_set(void __iomem *sr, u32 int0)
+{
+ writel(int0, sr + SP_I2C_INT_EN0_REG);
+}
+
+static void sp_i2cm_int_en1_set(void __iomem *sr, u32 rdata_en)
+{
+ writel(rdata_en, sr + SP_I2C_INT_EN1_REG);
+}
+
+static void sp_i2cm_int_en2_set(void __iomem *sr, u32 overflow_en)
+{
+ writel(overflow_en, sr + SP_I2C_INT_EN2_REG);
+}
+
+static void sp_i2cm_enable(unsigned int device_id, void *membase)
+{
+ writel(SP_I2C_CLKEN(device_id, 1), membase + SP_I2C_POWER_CLKEN3);
+ writel(SP_I2C_GCLKEN(device_id, 0), membase + SP_I2C_POWER_GCLKEN3);
+ writel(SP_I2C_RESET(device_id, 0), membase + SP_I2C_POWER_RESET3);
+}
+
+static void sp_i2cm_manual_trigger(void __iomem *sr)
+{
+ u32 val;
+
+ val = readl(sr + SP_I2C_MOD_REG);
+ val |= SP_I2C_MODE_MANUAL_TRIG;
+ writel(val, sr + SP_I2C_MOD_REG);
+}
+
+static void sp_i2cm_int_en0_with_thershold_set(void __iomem *sr, unsigned int int0,
+ unsigned char threshold)
+{
+ u32 val;
+
+ int0 &= ~SP_I2C_EN0_CTL_EMP_THOLD;
+ val = int0 | FIELD_PREP(SP_I2C_EN0_CTL_EMP_THOLD, threshold);
+ writel(val, sr + SP_I2C_INT_EN0_REG);
+}
+
+static void sp_i2cm_dma_mode_enable(void __iomem *sr)
+{
+ u32 val;
+
+ val = readl(sr + SP_I2C_MOD_REG);
+ val |= SP_I2C_MODE_DMA_MODE;
+ writel(val, sr + SP_I2C_MOD_REG);
+}
+
+static unsigned int sp_i2cm_get_dma_int_flag(void __iomem *sr_dma)
+{
+ return readl(sr_dma + SP_I2C_INT_REG);
+}
+
+static void sp_i2cm_dma_int_flag_clear(void __iomem *sr_dma, unsigned int flag)
+{
+ u32 val;
+
+ val = readl(sr_dma + SP_I2C_DMA_FLAG_REG);
+ val |= flag;
+ writel(val, sr_dma + SP_I2C_DMA_FLAG_REG);
+}
+
+static void sp_i2cm_dma_addr_set(void __iomem *sr_dma, dma_addr_t addr)
+{
+ writel(addr, sr_dma + SP_I2C_DMA_ADDR_REG);
+}
+
+static void sp_i2cm_dma_length_set(void __iomem *sr_dma, unsigned int length)
+{
+ length &= (0xFFFF); //only support 16 bit
+ writel(length, sr_dma + SP_I2C_DMA_LEN_REG);
+}
+
+static void sp_i2cm_dma_rw_mode_set(void __iomem *sr_dma, enum sp_i2c_dma_mode rw_mode)
+{
+ u32 val;
+
+ val = readl(sr_dma + SP_I2C_DMA_CONF_REG);
+ switch (rw_mode) {
+ default:
+ case I2C_DMA_WRITE_MODE:
+ val |= SP_I2C_DMA_CFG_DMA_MODE;
+ break;
+ case I2C_DMA_READ_MODE:
+ val &= (~SP_I2C_DMA_CFG_DMA_MODE);
+ break;
+ }
+ writel(val, sr_dma + SP_I2C_DMA_CONF_REG);
+}
+
+static void sp_i2cm_dma_int_en_set(void __iomem *sr_dma, unsigned int dma_int)
+{
+ writel(dma_int, sr_dma + SP_I2C_DMA_INT_EN_REG);
+}
+
+static void sp_i2cm_dma_go_set(void __iomem *sr_dma)
+{
+ u32 val;
+
+ val = readl(sr_dma + SP_I2C_DMA_CONF_REG);
+ val |= SP_I2C_DMA_CFG_DMA_GO;
+ writel(val, sr_dma + SP_I2C_DMA_CONF_REG);
+}
+
+static void _sp_i2cm_intflag_check(struct sp_i2c_dev *spi2c, struct sp_i2c_irq_event *spi2c_irq)
+{
+ void __iomem *sr = spi2c->i2c_regs;
+ unsigned int int_flag = 0;
+ unsigned int overflow_flag = 0;
+
+ int_flag = sp_i2cm_get_int_flag(sr);
+
+ if (int_flag & SP_I2C_INT_DONE_FLAG)
+ spi2c_irq->irq_flag.active_done = 1;
+ else
+ spi2c_irq->irq_flag.active_done = 0;
+
+ if (int_flag & SP_I2C_INT_ADDRESS_NACK_FLAG)
+ spi2c_irq->irq_flag.addr_nack = 1;
+ else
+ spi2c_irq->irq_flag.addr_nack = 0;
+
+ if (int_flag & SP_I2C_INT_DATA_NACK_FLAG)
+ spi2c_irq->irq_flag.data_nack = 1;
+ else
+ spi2c_irq->irq_flag.data_nack = 0;
+ // write use
+ if (int_flag & SP_I2C_INT_EMPTY_THRESHOLD_FLAG)
+ spi2c_irq->irq_flag.emp_thold = 1;
+ else
+ spi2c_irq->irq_flag.emp_thold = 0;
+ // write use
+ if (int_flag & SP_I2C_INT_EMPTY_FLAG)
+ spi2c_irq->irq_flag.fifo_empty = 1;
+ else
+ spi2c_irq->irq_flag.fifo_empty = 0;
+ // write use (for debug)
+ if (int_flag & SP_I2C_INT_FULL_FLAG)
+ spi2c_irq->irq_flag.fifo_full = 1;
+ else
+ spi2c_irq->irq_flag.fifo_full = 0;
+
+ if (int_flag & SP_I2C_INT_SCL_HOLD_TOO_LONG_FLAG)
+ spi2c_irq->irq_flag.scl_hold_too_long = 1;
+ else
+ spi2c_irq->irq_flag.scl_hold_too_long = 0;
+
+ sp_i2cm_status_clear(sr, SP_I2C_CTL1_ALL_CLR);
+
+ overflow_flag = sp_i2cm_over_flag_get(sr);
+
+ if (overflow_flag)
+ spi2c_irq->irq_flag.read_over_flow = 1;
+ else
+ spi2c_irq->irq_flag.read_over_flow = 0;
+}
+
+static void _sp_i2cm_dma_intflag_check(struct sp_i2c_dev *spi2c,
+ struct sp_i2c_irq_event *spi2c_irq)
+{
+ void __iomem *sr_dma = spi2c->i2c_dma_regs;
+ u32 int_flag = 0;
+
+ int_flag = sp_i2cm_get_dma_int_flag(sr_dma);
+
+ if (int_flag & SP_I2C_DMA_INT_DMA_DONE_FLAG)
+ spi2c_irq->irq_dma_flag.dma_done = 1;
+ else
+ spi2c_irq->irq_dma_flag.dma_done = 0;
+
+ if (int_flag & SP_I2C_DMA_INT_WCNT_ERROR_FLAG)
+ spi2c_irq->irq_dma_flag.write_cnt_err = 1;
+ else
+ spi2c_irq->irq_dma_flag.write_cnt_err = 0;
+
+ if (int_flag & SP_I2C_DMA_INT_WB_EN_ERROR_FLAG)
+ spi2c_irq->irq_dma_flag.wb_en_err = 1;
+ else
+ spi2c_irq->irq_dma_flag.wb_en_err = 0;
+
+ if (int_flag & SP_I2C_DMA_INT_GDMA_TIMEOUT_FLAG)
+ spi2c_irq->irq_dma_flag.gdma_timeout = 1;
+ else
+ spi2c_irq->irq_dma_flag.gdma_timeout = 0;
+
+ if (int_flag & SP_I2C_DMA_INT_IP_TIMEOUT_FLAG)
+ spi2c_irq->irq_dma_flag.ipt_timerout = 1;
+ else
+ spi2c_irq->irq_dma_flag.ipt_timerout = 0;
+
+ if (int_flag & SP_I2C_DMA_INT_LENGTH0_FLAG)
+ spi2c_irq->irq_dma_flag.dma_ligth = 1;
+ else
+ spi2c_irq->irq_dma_flag.dma_ligth = 0;
+
+ sp_i2cm_dma_int_flag_clear(sr_dma, 0x7F); //write 1 to clear
+}
+
+static irqreturn_t _sp_i2cm_irqevent_handler(int irq, void *args)
+{
+ struct sp_i2c_dev *spi2c = args;
+ struct sp_i2c_irq_event *spi2c_irq = &spi2c->spi2c_irq;
+ void __iomem *sr = spi2c->i2c_regs;
+ unsigned char r_data[SP_I2C_BURST_RDATA_BYTES] = {0};
+ unsigned char w_data[32] = {0};
+ unsigned int rdata_flag = 0;
+ unsigned int bit_index = 0;
+ int i = 0, j = 0, k = 0;
+
+ _sp_i2cm_intflag_check(spi2c, spi2c_irq);
+
+switch (spi2c_irq->rw_state) {
+case SPI2C_STATE_WR:
+case SPI2C_STATE_DMA_WR:
+ if (spi2c_irq->irq_flag.active_done) {
+ spi2c_irq->ret = SPI2C_SUCCESS;
+ wake_up(&spi2c->wait);
+ } else if (spi2c_irq->irq_flag.addr_nack || spi2c_irq->irq_flag.data_nack) {
+ if (spi2c_irq->rw_state == SPI2C_STATE_DMA_WR)
+ dev_err(spi2c->dev, "DMA wtire NACK!!\n");
+ else
+ dev_err(spi2c->dev, "wtire NACK!!\n");
+
+ spi2c_irq->ret = -ENXIO;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+ } else if (spi2c_irq->irq_flag.scl_hold_too_long) {
+ spi2c_irq->ret = -EINVAL;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+ } else if (spi2c_irq->irq_flag.fifo_empty) {
+ spi2c_irq->ret = -ENXIO;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+ } else if ((spi2c_irq->burst_cnt > 0) &&
+ (spi2c_irq->rw_state == SPI2C_STATE_WR)) {
+ if (spi2c_irq->irq_flag.emp_thold) {
+ for (i = 0; i < SP_I2C_EMP_THOLD; i++) {
+ for (j = 0; j < 4; j++) {
+ if (spi2c_irq->data_index >=
+ spi2c_irq->data_total_len)
+ w_data[j] = 0;
+ else
+ w_data[j] =
+ spi2c_irq->data_buf[spi2c_irq->data_index];
+ spi2c_irq->data_index++;
+ }
+ sp_i2cm_data0_set(sr, w_data);
+ spi2c_irq->burst_cnt--;
+ if (spi2c_irq->burst_cnt == 0) {
+ sp_i2cm_int_en0_disable(sr,
+ SP_I2C_EN0_EMP_THOLD_INT |
+ SP_I2C_EN0_EMPTY_INT);
+ break;
+ }
+ }
+ sp_i2cm_status_clear(sr, SP_I2C_CTL1_EMP_THOLD_CLR);
+ }
+ }
+ break;
+
+case SPI2C_STATE_RD:
+case SPI2C_STATE_DMA_RD:
+ if (spi2c_irq->irq_flag.addr_nack || spi2c_irq->irq_flag.data_nack) {
+ if (spi2c_irq->rw_state == SPI2C_STATE_DMA_RD)
+ dev_err(spi2c->dev, "DMA read NACK!!\n");
+ else
+ dev_err(spi2c->dev, "read NACK!!\n");
+
+ spi2c_irq->ret = -ENXIO;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+ } else if (spi2c_irq->irq_flag.scl_hold_too_long) {
+ spi2c_irq->ret = -EINVAL;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+ } else if (spi2c_irq->irq_flag.read_over_flow) {
+ spi2c_irq->ret = -EINVAL;
+ spi2c_irq->irq_flag.active_done = 1;
+ wake_up(&spi2c->wait);
+ sp_i2cm_reset(sr);
+} else {
+ if (spi2c_irq->burst_cnt > 0 && spi2c_irq->rw_state == SPI2C_STATE_RD) {
+ sp_i2cm_rdata_flag_get(sr, &rdata_flag);
+ for (i = 0; i < (32 / SP_I2C_BURST_RDATA_BYTES); i++) {
+ bit_index = (SP_I2C_BURST_RDATA_BYTES - 1) + (SP_I2C_BURST_RDATA_BYTES * i);
+ if (rdata_flag & (1 << bit_index)) {
+ for (j = 0; j < (SP_I2C_BURST_RDATA_BYTES / 4); j++) {
+ k = spi2c_irq->reg_data_index + j;
+ if (k >= 8)
+ k -= 8;
+
+ sp_i2cm_data_get(sr, k, &spi2c_irq->data_buf
+ [spi2c_irq->data_index]);
+ spi2c_irq->data_index += 4;
+ }
+ sp_i2cm_rdata_flag_clear(sr, (((1 << SP_I2C_BURST_RDATA_BYTES) -
+ 1) << (SP_I2C_BURST_RDATA_BYTES * i)));
+ spi2c_irq->reg_data_index += (SP_I2C_BURST_RDATA_BYTES / 4);
+ if (spi2c_irq->reg_data_index >= 8)
+ spi2c_irq->burst_cnt--;
+ }
+ }
+ }
+ if (spi2c_irq->irq_flag.active_done) {
+ if (spi2c_irq->burst_remainder && spi2c_irq->rw_state == SPI2C_STATE_RD) {
+ j = 0;
+ for (i = 0; i < (SP_I2C_BURST_RDATA_BYTES / 4); i++) {
+ k = spi2c_irq->reg_data_index + i;
+ if (k >= 8)
+ k -= 8;
+ sp_i2cm_data_get(sr, k, &r_data[j]);
+ j += 4;
+ }
+
+ for (i = 0; i < spi2c_irq->burst_remainder; i++)
+ spi2c_irq->data_buf[spi2c_irq->data_index + i] = r_data[i];
+ }
+ spi2c_irq->ret = SPI2C_SUCCESS;
+ wake_up(&spi2c->wait);
+ }
+ }
+ break;
+
+default:
+ break;
+}
+
+ _sp_i2cm_dma_intflag_check(spi2c, spi2c_irq);
+ switch (spi2c_irq->rw_state) {
+ case SPI2C_STATE_DMA_WR:
+ if (spi2c_irq->irq_dma_flag.dma_done) {
+ spi2c_irq->ret = SPI2C_SUCCESS;
+ wake_up(&spi2c->wait);
+ }
+ break;
+ case SPI2C_STATE_DMA_RD:
+ if (spi2c_irq->irq_dma_flag.dma_done) {
+ spi2c_irq->ret = SPI2C_SUCCESS;
+ wake_up(&spi2c->wait);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int _sp_i2cm_init(unsigned int device_id, struct sp_i2c_dev *spi2c)
+{
+ void __iomem *sr = spi2c->i2c_regs;
+
+ if (device_id >= spi2c->total_port)
+ return -ENXIO;
+
+ sp_i2cm_reset(sr);
+ return SPI2C_SUCCESS;
+}
+
+static int _sp_i2cm_get_resources(struct platform_device *pdev, struct sp_i2c_dev *spi2c)
+{
+ int ret;
+
+ spi2c->i2c_regs = devm_platform_ioremap_resource_byname(pdev, "i2cm");
+ if (IS_ERR(spi2c->i2c_regs))
+ return dev_err_probe(&pdev->dev, PTR_ERR(spi2c->i2c_regs), "regs get fail\n");
+
+ spi2c->i2c_dma_regs = devm_platform_ioremap_resource_byname(pdev, "i2cmdma");
+ if (IS_ERR(spi2c->i2c_dma_regs))
+ return dev_err_probe(&pdev->dev, PTR_ERR(spi2c->i2c_dma_regs), "regs get fail\n");
+
+ spi2c->i2c_power_regs = devm_platform_ioremap_resource_byname(pdev, "i2cdmapower");
+ if (IS_ERR(spi2c->i2c_power_regs))
+ spi2c->i2c_power_regs = 0;
+
+ spi2c->irq = platform_get_irq(pdev, 0);
+ if (spi2c->irq < 0)
+ return spi2c->irq;
+
+ ret = devm_request_irq(&pdev->dev, spi2c->irq, _sp_i2cm_irqevent_handler,
+ IRQF_TRIGGER_HIGH, pdev->name, spi2c);
+ if (ret)
+ return ret;
+
+ return SPI2C_SUCCESS;
+}
+
+static int sp_i2cm_read(struct sp_i2c_cmd *spi2c_cmd, struct sp_i2c_dev *spi2c)
+{
+ void __iomem *sr = spi2c->i2c_regs;
+ struct sp_i2c_irq_event *spi2c_irq = &spi2c->spi2c_irq;
+ unsigned int int0 = 0, int1 = 0, int2 = 0;
+ unsigned int burst_cnt = 0, burst_r = 0;
+ unsigned int write_cnt = 0;
+ unsigned int read_cnt = 0;
+ int ret = SPI2C_SUCCESS;
+
+ if (spi2c_irq->busy || spi2c_cmd->dev_id > spi2c->total_port) {
+ dev_err(spi2c->dev, "IO error !!\n");
+ return -ENXIO;
+ }
+
+ memset(spi2c_irq, 0, sizeof(*spi2c_irq));
+ spi2c_irq->busy = 1;
+
+ write_cnt = spi2c_cmd->write_cnt;
+ read_cnt = spi2c_cmd->read_cnt;
+
+ if (spi2c_cmd->restart_en) {
+ if (write_cnt > 32) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev,
+ "I2C write count is invalid !! write count=%d\n", write_cnt);
+ return -EINVAL;
+ }
+ }
+
+ if (read_cnt > 0xFFFF || read_cnt == 0) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev,
+ "I2C read count is invalid !! read count=%d\n", read_cnt);
+ return -EINVAL;
+ }
+
+ burst_cnt = read_cnt / SP_I2C_BURST_RDATA_BYTES;
+ burst_r = read_cnt % SP_I2C_BURST_RDATA_BYTES;
+
+ int0 = (SP_I2C_EN0_SCL_HOLD_TOO_LONG_INT | SP_I2C_EN0_EMPTY_INT | SP_I2C_EN0_DATA_NACK_INT
+ | SP_I2C_EN0_ADDRESS_NACK_INT | SP_I2C_EN0_DONE_INT);
+ if (burst_cnt) {
+ int1 = SP_I2C_BURST_RDATA_FLAG;
+ int2 = SP_I2C_BURST_RDATA_ALL_FLAG;
+ }
+
+ spi2c_irq->rw_state = SPI2C_STATE_RD;
+ spi2c_irq->burst_cnt = burst_cnt;
+ spi2c_irq->burst_remainder = burst_r;
+ spi2c_irq->data_index = 0;
+ spi2c_irq->reg_data_index = 0;
+ spi2c_irq->data_total_len = read_cnt;
+ spi2c_irq->data_buf = spi2c_cmd->read_data;
+
+ sp_i2cm_reset(sr);
+ sp_i2cm_clock_freq_set(sr, spi2c_cmd->freq);
+ sp_i2cm_slave_addr_set(sr, spi2c_cmd->slave_addr);
+ sp_i2cm_scl_delay_set(sr, SP_I2C_SCL_DELAY);
+ sp_i2cm_trans_cnt_set(sr, write_cnt, read_cnt);
+ sp_i2cm_active_mode_set(sr, I2C_TRIGGER);
+
+ if (spi2c_cmd->restart_en) {
+ write_cnt = spi2c_cmd->write_cnt / 4;
+ if (spi2c_cmd->write_cnt % 4)
+ write_cnt += 1;
+
+ sp_i2cm_data_tx(sr, spi2c_cmd->write_data, write_cnt);
+ sp_i2cm_rw_mode_set(sr, I2C_RESTART_MODE);
+ } else {
+ sp_i2cm_rw_mode_set(sr, I2C_READ_MODE);
+ }
+
+ sp_i2cm_int_en0_set(sr, int0);
+ sp_i2cm_int_en1_set(sr, int1);
+ sp_i2cm_int_en2_set(sr, int2);
+ sp_i2cm_manual_trigger(sr); //start send data
+
+ ret = wait_event_timeout(spi2c->wait, spi2c_irq->irq_flag.active_done,
+ (SP_I2C_SLEEP_TIMEOUT * HZ) / 500);
+ if (ret == 0) {
+ dev_err(spi2c->dev, "I2C read timeout !!\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = spi2c_irq->ret;
+ }
+ sp_i2cm_reset(sr);
+ spi2c_irq->rw_state = SPI2C_STATE_IDLE;
+ spi2c_irq->busy = 0;
+
+ return ret;
+}
+
+static int sp_i2cm_write(struct sp_i2c_cmd *spi2c_cmd, struct sp_i2c_dev *spi2c)
+{
+ struct sp_i2c_irq_event *spi2c_irq = &spi2c->spi2c_irq;
+ void __iomem *sr = spi2c->i2c_regs;
+ unsigned int write_cnt = 0;
+ unsigned int burst_cnt = 0;
+ unsigned int int0 = 0;
+ int ret = SPI2C_SUCCESS;
+ int i = 0;
+
+ if (spi2c_irq->busy || spi2c_cmd->dev_id > spi2c->total_port) {
+ dev_err(spi2c->dev, "IO error !!\n");
+ return -ENXIO;
+ }
+
+ memset(spi2c_irq, 0, sizeof(*spi2c_irq));
+ spi2c_irq->busy = 1;
+
+ write_cnt = spi2c_cmd->write_cnt;
+
+ if (write_cnt > 0xFFFF) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev,
+ "I2C write count is invalid !! write count=%d\n", write_cnt);
+ return -EINVAL;
+ }
+
+ burst_cnt = write_cnt / 4;
+ if (write_cnt % 4)
+ burst_cnt += 1;
+
+ int0 = (SP_I2C_EN0_SCL_HOLD_TOO_LONG_INT | SP_I2C_EN0_EMPTY_INT | SP_I2C_EN0_DATA_NACK_INT
+ | SP_I2C_EN0_ADDRESS_NACK_INT | SP_I2C_EN0_DONE_INT);
+
+ if (burst_cnt)
+ int0 |= SP_I2C_EN0_EMP_THOLD_INT;
+
+ spi2c_irq->rw_state = SPI2C_STATE_WR;
+ spi2c_irq->burst_cnt = burst_cnt;
+ spi2c_irq->data_index = i;
+ spi2c_irq->data_total_len = write_cnt;
+ spi2c_irq->data_buf = spi2c_cmd->write_data;
+
+ sp_i2cm_reset(sr);
+ sp_i2cm_clock_freq_set(sr, spi2c_cmd->freq);
+ sp_i2cm_slave_addr_set(sr, spi2c_cmd->slave_addr);
+ sp_i2cm_scl_delay_set(sr, SP_I2C_SCL_DELAY);
+ sp_i2cm_trans_cnt_set(sr, write_cnt, 0);
+ sp_i2cm_active_mode_set(sr, I2C_TRIGGER);
+ sp_i2cm_rw_mode_set(sr, I2C_WRITE_MODE);
+ sp_i2cm_data_tx(sr, spi2c_cmd->write_data, burst_cnt);
+
+ if (burst_cnt)
+ sp_i2cm_int_en0_with_thershold_set(sr, int0, SP_I2C_EMP_THOLD);
+ else
+ sp_i2cm_int_en0_set(sr, int0);
+
+ sp_i2cm_manual_trigger(sr); //start send data
+
+ ret = wait_event_timeout(spi2c->wait, spi2c_irq->irq_flag.active_done,
+ (SP_I2C_SLEEP_TIMEOUT * HZ) / 500);
+ if (ret == 0) {
+ dev_err(spi2c->dev, "I2C write timeout !!\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = spi2c_irq->ret;
+ }
+ sp_i2cm_reset(sr);
+ spi2c_irq->rw_state = SPI2C_STATE_IDLE;
+ spi2c_irq->busy = 0;
+
+ return ret;
+}
+
+static int sp_i2cm_dma_write(struct sp_i2c_cmd *spi2c_cmd, struct sp_i2c_dev *spi2c)
+{
+ struct sp_i2c_irq_event *spi2c_irq = &spi2c->spi2c_irq;
+ void __iomem *sr_dma = spi2c->i2c_dma_regs;
+ void __iomem *sr = spi2c->i2c_regs;
+ unsigned int int0 = 0;
+ unsigned int dma_int = 0;
+ int ret = SPI2C_SUCCESS;
+ dma_addr_t dma_w_addr = 0;
+
+ if (spi2c_irq->busy || spi2c_cmd->dev_id > spi2c->total_port) {
+ dev_err(spi2c->dev, "IO error !!\n");
+ return -ENXIO;
+ }
+
+ if (spi2c->mode == SP_I2C_DMA_POWER_SW && spi2c->i2c_power_regs != 0)
+ sp_i2cm_enable(0, spi2c->i2c_power_regs);
+
+ memset(spi2c_irq, 0, sizeof(*spi2c_irq));
+ spi2c_irq->busy = 1;
+
+ if (spi2c_cmd->write_cnt > 0xFFFF) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev, "write count = %d is invalid!\n", spi2c_cmd->write_cnt);
+ return -EINVAL;
+ }
+
+ spi2c_irq->rw_state = SPI2C_STATE_DMA_WR;
+
+ dma_w_addr = dma_map_single(spi2c->dev, spi2c_cmd->write_data,
+ spi2c_cmd->write_cnt, DMA_TO_DEVICE);
+
+ if (dma_mapping_error(spi2c->dev, dma_w_addr))
+ return -ENOMEM;
+
+ int0 = (SP_I2C_EN0_SCL_HOLD_TOO_LONG_INT | SP_I2C_EN0_EMPTY_INT
+ | SP_I2C_EN0_DATA_NACK_INT | SP_I2C_EN0_ADDRESS_NACK_INT | SP_I2C_EN0_DONE_INT);
+
+ dma_int = SP_I2C_DMA_EN_DMA_DONE_INT;
+
+ sp_i2cm_reset(sr);
+ sp_i2cm_dma_mode_enable(sr);
+ sp_i2cm_clock_freq_set(sr, spi2c_cmd->freq);
+ sp_i2cm_slave_addr_set(sr, spi2c_cmd->slave_addr);
+ sp_i2cm_scl_delay_set(sr, SP_I2C_SCL_DELAY);
+ sp_i2cm_active_mode_set(sr, I2C_AUTO);
+ sp_i2cm_rw_mode_set(sr, I2C_WRITE_MODE);
+ sp_i2cm_int_en0_set(sr, int0);
+
+ sp_i2cm_dma_addr_set(sr_dma, dma_w_addr);
+ sp_i2cm_dma_length_set(sr_dma, spi2c_cmd->write_cnt);
+ sp_i2cm_dma_rw_mode_set(sr_dma, I2C_DMA_READ_MODE);
+ sp_i2cm_dma_int_en_set(sr_dma, dma_int);
+ sp_i2cm_dma_go_set(sr_dma);
+
+ ret = wait_event_timeout(spi2c->wait, spi2c_irq->irq_dma_flag.dma_done,
+ (SP_I2C_SLEEP_TIMEOUT * HZ) / 200);
+ if (ret == 0) {
+ dev_err(spi2c->dev, "I2C DMA write timeout !!\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = spi2c_irq->ret;
+ }
+ sp_i2cm_status_clear(sr, 0xFFFFFFFF);
+
+ spi2c_irq->rw_state = SPI2C_STATE_IDLE;
+ spi2c_irq->busy = 0;
+
+ sp_i2cm_reset(sr);
+
+ return ret;
+}
+
+static int sp_i2cm_dma_read(struct sp_i2c_cmd *spi2c_cmd, struct sp_i2c_dev *spi2c)
+{
+ struct sp_i2c_irq_event *spi2c_irq = &spi2c->spi2c_irq;
+ void __iomem *sr_dma = spi2c->i2c_dma_regs;
+ void __iomem *sr = spi2c->i2c_regs;
+ unsigned int int0 = 0, int1 = 0, int2 = 0;
+ unsigned int write_cnt = 0;
+ unsigned int read_cnt = 0;
+ unsigned int dma_int = 0;
+ int ret = SPI2C_SUCCESS;
+ dma_addr_t dma_r_addr = 0;
+
+ if (spi2c_irq->busy || spi2c_cmd->dev_id > spi2c->total_port) {
+ dev_err(spi2c->dev, "IO error !!\n");
+ return -ENXIO;
+ }
+
+ if (spi2c->mode == SP_I2C_DMA_POWER_SW && spi2c->i2c_power_regs != 0)
+ sp_i2cm_enable(0, spi2c->i2c_power_regs);
+
+ memset(spi2c_irq, 0, sizeof(*spi2c_irq));
+ spi2c_irq->busy = 1;
+
+ write_cnt = spi2c_cmd->write_cnt;
+ read_cnt = spi2c_cmd->read_cnt;
+
+ if (spi2c_cmd->restart_en) {
+ if (write_cnt > 32) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev,
+ "I2C write count is invalid !! write count=%d\n", write_cnt);
+ return -EINVAL;
+ }
+ }
+
+ if (read_cnt > 0xFFFF || read_cnt == 0) {
+ spi2c_irq->busy = 0;
+ dev_err(spi2c->dev,
+ "I2C read count is invalid !! read count=%d\n", read_cnt);
+ return -EINVAL;
+ }
+
+ dma_r_addr = dma_map_single(spi2c->dev, spi2c_cmd->read_data,
+ spi2c_cmd->read_cnt, DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(spi2c->dev, dma_r_addr))
+ return -ENOMEM;
+
+ int0 = (SP_I2C_EN0_SCL_HOLD_TOO_LONG_INT | SP_I2C_EN0_EMPTY_INT | SP_I2C_EN0_DATA_NACK_INT
+ | SP_I2C_EN0_ADDRESS_NACK_INT | SP_I2C_EN0_DONE_INT);
+
+ dma_int = SP_I2C_DMA_EN_DMA_DONE_INT;
+
+ spi2c_irq->rw_state = SPI2C_STATE_DMA_RD;
+ spi2c_irq->data_index = 0;
+ spi2c_irq->reg_data_index = 0;
+ spi2c_irq->data_total_len = read_cnt;
+
+ sp_i2cm_reset(sr);
+ sp_i2cm_dma_mode_enable(sr);
+ sp_i2cm_clock_freq_set(sr, spi2c_cmd->freq);
+ sp_i2cm_slave_addr_set(sr, spi2c_cmd->slave_addr);
+ sp_i2cm_scl_delay_set(sr, SP_I2C_SCL_DELAY);
+
+ if (spi2c_cmd->restart_en) {
+ sp_i2cm_active_mode_set(sr, I2C_TRIGGER);
+ sp_i2cm_rw_mode_set(sr, I2C_RESTART_MODE);
+ sp_i2cm_trans_cnt_set(sr, write_cnt, read_cnt);
+ write_cnt = spi2c_cmd->write_cnt / 4;
+ if (spi2c_cmd->write_cnt % 4)
+ write_cnt += 1;
+
+ sp_i2cm_data_tx(sr, spi2c_cmd->write_data, write_cnt);
+
+ } else {
+ sp_i2cm_active_mode_set(sr, I2C_AUTO);
+ sp_i2cm_rw_mode_set(sr, I2C_READ_MODE);
+ }
+
+ sp_i2cm_int_en0_set(sr, int0);
+ sp_i2cm_int_en1_set(sr, int1);
+ sp_i2cm_int_en2_set(sr, int2);
+
+ sp_i2cm_dma_addr_set(sr_dma, dma_r_addr);
+ sp_i2cm_dma_length_set(sr_dma, spi2c_cmd->read_cnt);
+ sp_i2cm_dma_rw_mode_set(sr_dma, I2C_DMA_WRITE_MODE);
+ sp_i2cm_dma_int_en_set(sr_dma, dma_int);
+ sp_i2cm_dma_go_set(sr_dma);
+
+ if (spi2c_cmd->restart_en)
+ sp_i2cm_manual_trigger(sr); //start send data
+
+ ret = wait_event_timeout(spi2c->wait, spi2c_irq->irq_dma_flag.dma_done,
+ (SP_I2C_SLEEP_TIMEOUT * HZ) / 200);
+ if (ret == 0) {
+ dev_err(spi2c->dev, "I2C DMA read timeout !!\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = spi2c_irq->ret;
+ }
+ sp_i2cm_status_clear(sr, 0xFFFFFFFF);
+
+ //copy data from virtual addr to spi2c_cmd->read_data
+ spi2c_irq->rw_state = SPI2C_STATE_IDLE;
+ spi2c_irq->busy = 0;
+
+ sp_i2cm_reset(sr);
+ return ret;
+}
+
+static int sp_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct sp_i2c_dev *spi2c = adap->algo_data;
+ struct sp_i2c_cmd *spi2c_cmd = &spi2c->spi2c_cmd;
+ unsigned char restart_w_data[32] = {0};
+ unsigned int restart_write_cnt = 0;
+ unsigned int restart_en = 0;
+ int ret = SPI2C_SUCCESS;
+ int i = 0;
+
+ ret = pm_runtime_get_sync(spi2c->dev);
+
+ if (num == 0)
+ return -EINVAL;
+
+ memset(spi2c_cmd, 0, sizeof(*spi2c_cmd));
+ spi2c_cmd->dev_id = adap->nr;
+
+ if (spi2c_cmd->freq > SP_I2C_FAST_FREQ)
+ spi2c_cmd->freq = SP_I2C_FAST_FREQ;
+ else
+ spi2c_cmd->freq = spi2c->i2c_clk_freq / 1000;
+
+ for (i = 0; i < num; i++) {
+ if (msgs[i].flags & I2C_M_TEN)
+ return -EINVAL;
+
+ spi2c_cmd->slave_addr = msgs[i].addr;
+ if (msgs[i].flags & I2C_M_NOSTART) {
+ restart_write_cnt = msgs[i].len;
+ for (i = 0; i < restart_write_cnt; i++)
+ restart_w_data[i] = msgs[i].buf[i];
+
+ restart_en = 1;
+ continue;
+ }
+ if (msgs[i].flags & I2C_M_RD) {
+ if (restart_en == 1) {
+ spi2c_cmd->write_cnt = restart_write_cnt;
+ spi2c_cmd->write_data = restart_w_data;
+ restart_en = 0;
+ spi2c_cmd->restart_en = 1;
+ }
+ spi2c_cmd->read_cnt = msgs[i].len;
+ spi2c_cmd->read_data = i2c_get_dma_safe_msg_buf(&msgs[i], 4);
+
+ if (spi2c_cmd->read_cnt < 4 || !spi2c_cmd->read_data) {
+ spi2c_cmd->read_data = msgs[i].buf;
+ ret = sp_i2cm_read(spi2c_cmd, spi2c);
+ } else {
+ ret = sp_i2cm_dma_read(spi2c_cmd, spi2c);
+ i2c_put_dma_safe_msg_buf(spi2c_cmd->read_data, &msgs[i], true);
+ }
+ } else {
+ spi2c_cmd->write_cnt = msgs[i].len;
+ spi2c_cmd->write_data = i2c_get_dma_safe_msg_buf(&msgs[i], 4);
+ if (spi2c_cmd->write_cnt < 4 || !spi2c_cmd->write_data) {
+ spi2c_cmd->write_data = msgs[i].buf;
+ ret = sp_i2cm_write(spi2c_cmd, spi2c);
+ } else {
+ ret = sp_i2cm_dma_write(spi2c_cmd, spi2c);
+ i2c_put_dma_safe_msg_buf(spi2c_cmd->write_data,
+ &msgs[i], true);
+ }
+ }
+ if (ret != SPI2C_SUCCESS)
+ return -EIO;
+ }
+
+ pm_runtime_put(spi2c->dev);
+ return num;
+}
+
+static u32 sp_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm sp_algorithm = {
+ .master_xfer = sp_master_xfer,
+ .functionality = sp_functionality,
+};
+
+static const struct i2c_compatible i2c_7021_compat = {
+ .mode = SP_I2C_DMA_POWER_SW,
+ .total_port = 4,
+};
+
+static const struct i2c_compatible i2c_645_compat = {
+ .mode = SP_I2C_DMA_POWER_NO_SW,
+ .total_port = 6,
+
+};
+
+static const struct of_device_id sp_i2c_of_match[] = {
+ { .compatible = "sunplus,sp7021-i2cm",
+ .data = &i2c_7021_compat, },
+ { .compatible = "sunplus,q645-i2cm",
+ .data = &i2c_645_compat, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sp_i2c_of_match);
+
+static void sp_i2c_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static void sp_i2c_reset_control_assert(void *data)
+{
+ reset_control_assert(data);
+}
+
+static int sp_i2c_probe(struct platform_device *pdev)
+{
+ struct sp_i2c_dev *spi2c;
+ struct i2c_adapter *p_adap;
+ unsigned int i2c_clk_freq;
+ int device_id = 0;
+ int ret = SPI2C_SUCCESS;
+ struct device *dev = &pdev->dev;
+ const struct i2c_compatible *dev_mode;
+
+ if (pdev->dev.of_node) {
+ pdev->id = of_alias_get_id(pdev->dev.of_node, "i2c");
+ device_id = pdev->id;
+ }
+
+ spi2c = devm_kzalloc(&pdev->dev, sizeof(*spi2c), GFP_KERNEL);
+ if (!spi2c)
+ return -ENOMEM;
+
+ if (!of_property_read_u32(pdev->dev.of_node, "clock-frequency", &i2c_clk_freq))
+ spi2c->i2c_clk_freq = i2c_clk_freq;
+ else
+ spi2c->i2c_clk_freq = SP_I2C_STD_FREQ * 1000;
+
+ spi2c->dev = &pdev->dev;
+ ret = _sp_i2cm_get_resources(pdev, spi2c);
+ if (ret != SPI2C_SUCCESS)
+ return ret;
+
+ spi2c->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(spi2c->clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(spi2c->clk), "err get clock\n");
+
+ spi2c->rstc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(spi2c->rstc))
+ return dev_err_probe(&pdev->dev, PTR_ERR(spi2c->rstc), "err get reset\n");
+
+ ret = clk_prepare_enable(spi2c->clk);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "failed to enable clk\n");
+
+ ret = devm_add_action_or_reset(dev, sp_i2c_disable_unprepare, spi2c->clk);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(spi2c->rstc);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "failed to deassert reset\n");
+
+ ret = devm_add_action_or_reset(dev, sp_i2c_reset_control_assert, spi2c->rstc);
+ if (ret)
+ return ret;
+
+ init_waitqueue_head(&spi2c->wait);
+
+ dev_mode = of_device_get_match_data(&pdev->dev);
+ spi2c->mode = dev_mode->mode;
+ spi2c->total_port = dev_mode->total_port;
+ p_adap = &spi2c->adap;
+ sprintf(p_adap->name, "%s%d", "sunplus-i2cm", device_id);
+ p_adap->algo = &sp_algorithm;
+ p_adap->algo_data = spi2c;
+ p_adap->nr = device_id;
+ p_adap->class = 0;
+ p_adap->retries = 5;
+ p_adap->dev.parent = &pdev->dev;
+ p_adap->dev.of_node = pdev->dev.of_node;
+
+ ret = i2c_add_numbered_adapter(p_adap);
+ ret = _sp_i2cm_init(device_id, spi2c);
+ platform_set_drvdata(pdev, spi2c);
+
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 5000);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ return ret;
+}
+
+static int sp_i2c_remove(struct platform_device *pdev)
+{
+ struct sp_i2c_dev *spi2c = platform_get_drvdata(pdev);
+ struct i2c_adapter *p_adap = &spi2c->adap;
+
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ i2c_del_adapter(p_adap);
+
+ return 0;
+}
+
+static int __maybe_unused sp_i2c_suspend(struct device *dev)
+{
+ struct sp_i2c_dev *spi2c = dev_get_drvdata(dev);
+ struct i2c_adapter *p_adap = &spi2c->adap;
+
+ if (p_adap->nr < spi2c->total_port)
+ reset_control_assert(spi2c->rstc);
+
+ return 0;
+}
+
+static int __maybe_unused sp_i2c_resume(struct device *dev)
+{
+ struct sp_i2c_dev *spi2c = dev_get_drvdata(dev);
+ struct i2c_adapter *p_adap = &spi2c->adap;
+
+ if (p_adap->nr < spi2c->total_port) {
+ reset_control_deassert(spi2c->rstc);
+ clk_prepare_enable(spi2c->clk);
+ }
+ return 0;
+}
+
+static int sp_i2c_runtime_suspend(struct device *dev)
+{
+ struct sp_i2c_dev *spi2c = dev_get_drvdata(dev);
+ struct i2c_adapter *p_adap = &spi2c->adap;
+
+ if (p_adap->nr < spi2c->total_port)
+ reset_control_assert(spi2c->rstc);
+
+ return 0;
+}
+
+static int sp_i2c_runtime_resume(struct device *dev)
+{
+ struct sp_i2c_dev *spi2c = dev_get_drvdata(dev);
+ struct i2c_adapter *p_adap = &spi2c->adap;
+
+ if (p_adap->nr < spi2c->total_port) {
+ reset_control_deassert(spi2c->rstc); //release reset
+ clk_prepare_enable(spi2c->clk); //enable clken and disable gclken
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops sp7021_i2c_pm_ops = {
+ SET_RUNTIME_PM_OPS(sp_i2c_runtime_suspend,
+ sp_i2c_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(sp_i2c_suspend, sp_i2c_resume)
+
+};
+
+#define sp_i2c_pm_ops (&sp7021_i2c_pm_ops)
+
+static struct platform_driver sp_i2c_driver = {
+ .probe = sp_i2c_probe,
+ .remove = sp_i2c_remove,
+ .driver = {
+ .name = "sunplus-i2cm",
+ .of_match_table = sp_i2c_of_match,
+ .pm = sp_i2c_pm_ops,
+ },
+};
+
+static int __init sp_i2c_adap_init(void)
+{
+ return platform_driver_register(&sp_i2c_driver);
+}
+module_init(sp_i2c_adap_init);
+
+static void __exit sp_i2c_adap_exit(void)
+{
+ platform_driver_unregister(&sp_i2c_driver);
+}
+module_exit(sp_i2c_adap_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Li-hao Kuo <lhjeff911@gmail.com>");
+MODULE_DESCRIPTION("Sunplus I2C Master Driver");
--
2.7.4
^ permalink raw reply related
* [PATCH v8 04/10] reset: Add Sunplus SP7021 reset driver
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add reset driver for Sunplus SP7021 SoC.
Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
MAINTAINERS | 1 +
drivers/reset/Kconfig | 9 +++
drivers/reset/Makefile | 1 +
drivers/reset/reset-sunplus.c | 130 ++++++++++++++++++++++++++++++++++
4 files changed, 141 insertions(+)
create mode 100644 drivers/reset/reset-sunplus.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 652f42cab..6caffd6d0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2662,6 +2662,7 @@ S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/reset/sp-sp7021.h
ARM/Synaptics SoC support
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index be799a5ab..fb14456a8 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -224,6 +224,15 @@ config RESET_SOCFPGA
This enables the reset driver for the SoCFPGA ARMv7 platforms. This
driver gets initialized early during platform init calls.
+config RESET_SUNPLUS
+ bool "Sunplus SoCs Reset Driver" if COMPILE_TEST
+ default ARCH_SUNPLUS
+ help
+ This enables the reset driver support for Sunplus SoCs.
+ The reset lines that can be asserted and deasserted by toggling bits
+ in a contiguous, exclusive register space. The register is HIWORD_MASKED,
+ which means each register hold 16 reset lines.
+
config RESET_SUNXI
bool "Allwinner SoCs Reset Driver" if COMPILE_TEST && !ARCH_SUNXI
default ARCH_SUNXI
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 21d46d886..f03403e97 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o
obj-$(CONFIG_RESET_SCMI) += reset-scmi.o
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o
+obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o
obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o
obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o
obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o
diff --git a/drivers/reset/reset-sunplus.c b/drivers/reset/reset-sunplus.c
new file mode 100644
index 000000000..113b36b57
--- /dev/null
+++ b/drivers/reset/reset-sunplus.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * SP7021 reset driver
+ *
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/reboot.h>
+
+/* HIWORD_MASK_REG BITS */
+#define BITS_PER_HWM_REG 16
+
+struct sp_reset {
+ struct reset_controller_dev rcdev;
+ struct notifier_block notifier;
+ void __iomem *base;
+};
+
+static inline struct sp_reset *to_sp_reset(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct sp_reset, rcdev);
+}
+
+static int sp_reset_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ struct sp_reset *reset = to_sp_reset(rcdev);
+ int index = id / BITS_PER_HWM_REG;
+ int shift = id % BITS_PER_HWM_REG;
+ u32 val;
+
+ val = (1 << (16 + shift)) | (assert << shift);
+ writel(val, reset->base + (index * 4));
+
+ return 0;
+}
+
+static int sp_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return sp_reset_update(rcdev, id, true);
+}
+
+static int sp_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return sp_reset_update(rcdev, id, false);
+}
+
+static int sp_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct sp_reset *reset = to_sp_reset(rcdev);
+ int index = id / BITS_PER_HWM_REG;
+ int shift = id % BITS_PER_HWM_REG;
+ u32 reg;
+
+ reg = readl(reset->base + (index * 4));
+
+ return !!(reg & BIT(shift));
+}
+
+static const struct reset_control_ops sp_reset_ops = {
+ .assert = sp_reset_assert,
+ .deassert = sp_reset_deassert,
+ .status = sp_reset_status,
+};
+
+static int sp_restart(struct notifier_block *nb, unsigned long mode,
+ void *cmd)
+{
+ struct sp_reset *reset = container_of(nb, struct sp_reset, notifier);
+
+ sp_reset_assert(&reset->rcdev, 0);
+ sp_reset_deassert(&reset->rcdev, 0);
+
+ return NOTIFY_DONE;
+}
+
+static int sp_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sp_reset *reset;
+ struct resource *res;
+ int ret;
+
+ reset = devm_kzalloc(dev, sizeof(*reset), GFP_KERNEL);
+ if (!reset)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ reset->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(reset->base))
+ return PTR_ERR(reset->base);
+
+ reset->rcdev.ops = &sp_reset_ops;
+ reset->rcdev.owner = THIS_MODULE;
+ reset->rcdev.of_node = dev->of_node;
+ reset->rcdev.nr_resets = resource_size(res) / 4 * BITS_PER_HWM_REG;
+
+ ret = devm_reset_controller_register(dev, &reset->rcdev);
+ if (ret)
+ return ret;
+
+ reset->notifier.notifier_call = sp_restart;
+ reset->notifier.priority = 192;
+
+ return register_restart_handler(&reset->notifier);
+}
+
+static const struct of_device_id sp_reset_dt_ids[] = {
+ {.compatible = "sunplus,sp7021-reset",},
+ { /* sentinel */ },
+};
+
+static struct platform_driver sp_reset_driver = {
+ .probe = sp_reset_probe,
+ .driver = {
+ .name = "sunplus-reset",
+ .of_match_table = sp_reset_dt_ids,
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_platform_driver(sp_reset_driver);
--
2.33.1
^ permalink raw reply related
* [PATCH v8 10/10] ARM: sp7021_defconfig: Add Sunplus SP7021 defconfig
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add generic Sunplus SP7021 based board defconfig
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
MAINTAINERS | 1 +
arch/arm/configs/multi_v7_defconfig | 1 +
arch/arm/configs/sp7021_defconfig | 61 +++++++++++++++++++++++++++++
3 files changed, 63 insertions(+)
create mode 100644 arch/arm/configs/sp7021_defconfig
diff --git a/MAINTAINERS b/MAINTAINERS
index 0ae537a41..9340f8760 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2664,6 +2664,7 @@ F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: arch/arm/configs/sp7021_*defconfig
F: arch/arm/mach-sunplus/
F: drivers/clk/clk-sp7021.c
F: drivers/irqchip/irq-sp7021-intc.c
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index b4f74454f..585e3b5d3 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -87,6 +87,7 @@ CONFIG_MACH_SPEAR1310=y
CONFIG_MACH_SPEAR1340=y
CONFIG_ARCH_STI=y
CONFIG_ARCH_STM32=y
+CONFIG_ARCH_SUNPLUS=y
CONFIG_ARCH_SUNXI=y
CONFIG_ARCH_TEGRA=y
CONFIG_ARCH_UNIPHIER=y
diff --git a/arch/arm/configs/sp7021_defconfig b/arch/arm/configs/sp7021_defconfig
new file mode 100644
index 000000000..cda16d33a
--- /dev/null
+++ b/arch/arm/configs/sp7021_defconfig
@@ -0,0 +1,61 @@
+CONFIG_SYSVIPC=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_PREEMPT=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_LOG_BUF_SHIFT=14
+# CONFIG_RD_GZIP is not set
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_XZ is not set
+# CONFIG_RD_LZO is not set
+# CONFIG_RD_LZ4 is not set
+CONFIG_CC_OPTIMIZE_FOR_SIZE=y
+CONFIG_PERF_EVENTS=y
+CONFIG_SLAB=y
+CONFIG_ARCH_SUNPLUS=y
+# CONFIG_VDSO is not set
+CONFIG_SMP=y
+CONFIG_HAVE_ARM_ARCH_TIMER=y
+CONFIG_THUMB2_KERNEL=y
+CONFIG_FORCE_MAX_ZONEORDER=12
+CONFIG_VFP=y
+CONFIG_NEON=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+CONFIG_UEVENT_HELPER=y
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_INPUT_SPARSEKMAP=y
+CONFIG_INPUT_EVDEV=y
+# CONFIG_INPUT_KEYBOARD is not set
+# CONFIG_INPUT_MOUSE is not set
+# CONFIG_LEGACY_PTYS is not set
+# CONFIG_HW_RANDOM is not set
+# CONFIG_HWMON is not set
+CONFIG_STAGING=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RESET_CONTROLLER=y
+CONFIG_EXT4_FS=y
+# CONFIG_DNOTIFY is not set
+CONFIG_FANOTIFY=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="utf8"
+CONFIG_EXFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+# CONFIG_MISC_FILESYSTEMS is not set
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_NLS_UTF8=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_FS=y
+CONFIG_DEBUG_USER=y
--
2.33.1
^ permalink raw reply related
* [PATCH v8 09/10] ARM: sunplus: Add initial support for Sunplus SP7021 SoC
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
This patch aims to add an initial support for Sunplus SP7021 SoC.
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
MAINTAINERS | 1 +
arch/arm/Kconfig | 2 ++
arch/arm/Makefile | 1 +
arch/arm/mach-sunplus/Kconfig | 26 ++++++++++++++++++++++++++
arch/arm/mach-sunplus/Makefile | 9 +++++++++
arch/arm/mach-sunplus/sp7021.c | 16 ++++++++++++++++
6 files changed, 55 insertions(+)
create mode 100644 arch/arm/mach-sunplus/Kconfig
create mode 100644 arch/arm/mach-sunplus/Makefile
create mode 100644 arch/arm/mach-sunplus/sp7021.c
diff --git a/MAINTAINERS b/MAINTAINERS
index febbd97bf..0ae537a41 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2664,6 +2664,7 @@ F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: arch/arm/mach-sunplus/
F: drivers/clk/clk-sp7021.c
F: drivers/irqchip/irq-sp7021-intc.c
F: drivers/reset/reset-sunplus.c
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 59baf6c13..8c7883b5a 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -689,6 +689,8 @@ source "arch/arm/mach-sti/Kconfig"
source "arch/arm/mach-stm32/Kconfig"
+source "arch/arm/mach-sunplus/Kconfig"
+
source "arch/arm/mach-sunxi/Kconfig"
source "arch/arm/mach-tegra/Kconfig"
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index 847c31e7c..973ffb830 100644
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -212,6 +212,7 @@ machine-$(CONFIG_ARCH_RENESAS) += shmobile
machine-$(CONFIG_ARCH_INTEL_SOCFPGA) += socfpga
machine-$(CONFIG_ARCH_STI) += sti
machine-$(CONFIG_ARCH_STM32) += stm32
+machine-$(CONFIG_ARCH_SUNPLUS) += sunplus
machine-$(CONFIG_ARCH_SUNXI) += sunxi
machine-$(CONFIG_ARCH_TEGRA) += tegra
machine-$(CONFIG_ARCH_U8500) += ux500
diff --git a/arch/arm/mach-sunplus/Kconfig b/arch/arm/mach-sunplus/Kconfig
new file mode 100644
index 000000000..e720606dd
--- /dev/null
+++ b/arch/arm/mach-sunplus/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+menuconfig ARCH_SUNPLUS
+ bool "Sunplus SoCs"
+ depends on ARCH_MULTI_V7
+ help
+ Support for Sunplus SoC family: SP7021 and succeeding SoC-based systems,
+ such as the Banana Pi BPI-F2S development board (and derivatives).
+ (<http://www.sinovoip.com.cn/ecp_view.asp?id=586>)
+ (<https://tibbo.com/store/plus1.html>)
+
+config SOC_SP7021
+ bool "Sunplus SP7021 SoC support"
+ depends on ARCH_SUNPLUS
+ default ARCH_SUNPLUS
+ select ARM_GIC
+ select ARM_PSCI
+ select PINCTRL
+ select PINCTRL_SPPCTL
+ select SERIAL_SUNPLUS
+ select SERIAL_SUNPLUS_CONSOLE
+ help
+ Support for Sunplus SP7021 SoC. It is based on ARM 4-core
+ Cortex-A7 with various peripherals (ex: I2C, SPI, SDIO,
+ Ethernet and etc.), FPGA interface, chip-to-chip bus.
+ It is designed for industrial control.
diff --git a/arch/arm/mach-sunplus/Makefile b/arch/arm/mach-sunplus/Makefile
new file mode 100644
index 000000000..c902580a7
--- /dev/null
+++ b/arch/arm/mach-sunplus/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the linux kernel.
+#
+
+# Object file lists.
+
+obj-$(CONFIG_SOC_SP7021) += sp7021.o
+
diff --git a/arch/arm/mach-sunplus/sp7021.c b/arch/arm/mach-sunplus/sp7021.c
new file mode 100644
index 000000000..774d0a5bd
--- /dev/null
+++ b/arch/arm/mach-sunplus/sp7021.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#include <linux/kernel.h>
+#include <asm/mach/arch.h>
+
+static const char *sp7021_compat[] __initconst = {
+ "sunplus,sp7021",
+ NULL
+};
+
+DT_MACHINE_START(SP7021_DT, "SP7021")
+ .dt_compat = sp7021_compat,
+MACHINE_END
--
2.33.1
^ permalink raw reply related
* [PATCH v8 00/10] Add Sunplus SP7021 SoC Support
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
This patch series add Sunplus SP7021 SoC support.
Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates many
peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and etc.) into a
single chip. It is designed for industrial control.
SP7021 consists of two chips (dies) in a package. One is called C-chip
(computing chip). It is a 4-core ARM Cortex A7 CPU. It adopts high-level
process (22 nm) for high performance computing. The other is called P-
chip (peripheral chip). It has many peripherals and an ARM A926 added
especially for real-time control. P-chip is made for customers. It adopts
low-level process (ex: 0.11 um) to reduce cost.
Refer to (for documentations):
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
Refer to (applications):
https://tibbo.com/store/plus1.html
Refer to (applications):
http://www.sinovoip.com.cn/ecp_view.asp?id=586
Changes in v8:
- clk-sp7021.c: fix the comments form Stephen Boyd
Changes in v7:
- sunplus,sp7021-clkc.yaml: Add clocks & clock-names
- clk-sp7021.c: fix the comments form Stephen Boyd
- irq-sp7021-intc.c: fix the comments from Marc
Changes in v6:
- reset-sunplus.c: fix the comments from Philipp
- irq-sp7021-intc.c: fix the comments from Marc
- mach-sunplus: fix the comments from Arnd
Changes in v5:
- reset-sunplus.c: fix strict checks
- clk/Kconfig: fix spell
- clk-sp7021.c: using bitfield ops, fix strict checks
- irqchip/Kconfig: fix spell
- irq-sp7021-intc.c: cleanup error path in probe, fix strict checks
- arm/Kconfig: fix spell & typo, remove CONFIG_SERIAL_SUNPLUS
- mach-sunplus/Kconfig: fix typo
- sp7021_defconfig: add CONFIG_SERIAL_SUNPLUS
Changes in v4:
- mach-sunplus: add initial support for SP7021
- sp7021_defconfig: add generic SP7021 defconfig
- reset-sunplus: remove Q645 support
- reset-sunplus.c: refine code based on Philipp's review
- clk-sp7021: clock defines add prefix, more clean up
Changes in v3:
- sp7021-intc: remove primary controller mode due to P-chip running Linux
not supported any more.
- sp7021-intc.h: removed, not set ext through the DT but sp_intc_set_ext()
- sunplus,sp7021-intc.yaml: update descriptions for above changes
- irq-sp7021-intc.c: more cleanup based on Marc's review
- all driver's Kconfig removed default, it's selected by platform config
Changes in v2:
- sunplus,sp7021-intc.yaml: add descrption for "#interrupt-cells", interrupts
- sunplus,sp7021-intc.yaml: drop "ext0-mask"/"ext1-mask" from DT
- sunplus,sp7021-intc.yaml: fix example.dt too long error
- irq-sp7021-intc.c: major rewrite
- all files with dual license
Qin Jian (10):
dt-bindings: vendor-prefixes: Add Sunplus
dt-bindings: arm: sunplus: Add bindings for Sunplus SP7021 SoC boards
dt-bindings: reset: Add bindings for SP7021 reset driver
reset: Add Sunplus SP7021 reset driver
dt-bindings: clock: Add bindings for SP7021 clock driver
clk: Add Sunplus SP7021 clock driver
dt-bindings: interrupt-controller: Add bindings for SP7021 interrupt
controller
irqchip: Add Sunplus SP7021 interrupt controller driver
ARM: sunplus: Add initial support for Sunplus SP7021 SoC
ARM: sp7021_defconfig: Add Sunplus SP7021 defconfig
.../bindings/arm/sunplus,sp7021.yaml | 27 +
.../bindings/clock/sunplus,sp7021-clkc.yaml | 52 ++
.../sunplus,sp7021-intc.yaml | 62 ++
.../bindings/reset/sunplus,reset.yaml | 38 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 17 +
arch/arm/Kconfig | 2 +
arch/arm/Makefile | 1 +
arch/arm/configs/multi_v7_defconfig | 1 +
arch/arm/configs/sp7021_defconfig | 61 ++
arch/arm/mach-sunplus/Kconfig | 26 +
arch/arm/mach-sunplus/Makefile | 9 +
arch/arm/mach-sunplus/sp7021.c | 16 +
drivers/clk/Kconfig | 9 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 753 ++++++++++++++++++
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021-intc.c | 288 +++++++
drivers/reset/Kconfig | 9 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-sunplus.c | 130 +++
include/dt-bindings/clock/sp-sp7021.h | 112 +++
include/dt-bindings/reset/sp-sp7021.h | 97 +++
24 files changed, 1724 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
create mode 100644 Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
create mode 100644 Documentation/devicetree/bindings/reset/sunplus,reset.yaml
create mode 100644 arch/arm/configs/sp7021_defconfig
create mode 100644 arch/arm/mach-sunplus/Kconfig
create mode 100644 arch/arm/mach-sunplus/Makefile
create mode 100644 arch/arm/mach-sunplus/sp7021.c
create mode 100644 drivers/clk/clk-sp7021.c
create mode 100644 drivers/irqchip/irq-sp7021-intc.c
create mode 100644 drivers/reset/reset-sunplus.c
create mode 100644 include/dt-bindings/clock/sp-sp7021.h
create mode 100644 include/dt-bindings/reset/sp-sp7021.h
--
2.33.1
^ permalink raw reply
* [PATCH v8 06/10] clk: Add Sunplus SP7021 clock driver
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add clock driver for Sunplus SP7021 SoC.
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
Fix the comments from Stephen Boyd.
---
MAINTAINERS | 1 +
drivers/clk/Kconfig | 9 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-sp7021.c | 753 +++++++++++++++++++++++++++++++++++++++
4 files changed, 764 insertions(+)
create mode 100644 drivers/clk/clk-sp7021.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 90ebb823f..5069f552f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2663,6 +2663,7 @@ W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: drivers/clk/clk-sp7021.c
F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index c5b3dc973..91656de22 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -334,6 +334,15 @@ config COMMON_CLK_VC5
This driver supports the IDT VersaClock 5 and VersaClock 6
programmable clock generators.
+config COMMON_CLK_SP7021
+ bool "Clock driver for Sunplus SP7021 SoC"
+ default SOC_SP7021
+ help
+ This driver supports the Sunplus SP7021 SoC clocks.
+ It implements SP7021 PLLs/gate.
+ Not all features of the PLL are currently supported
+ by the driver.
+
config COMMON_CLK_STM32MP157
def_bool COMMON_CLK && MACH_STM32MP157
help
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e42312121..f15bb5070 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
obj-$(CONFIG_COMMON_CLK_SI544) += clk-si544.o
obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
+obj-$(CONFIG_COMMON_CLK_SP7021) += clk-sp7021.o
obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o
obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o
obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o
diff --git a/drivers/clk/clk-sp7021.c b/drivers/clk/clk-sp7021.c
new file mode 100644
index 000000000..8a3e28a2a
--- /dev/null
+++ b/drivers/clk/clk-sp7021.c
@@ -0,0 +1,753 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+//#define DEBUG
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/bitfield.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <dt-bindings/clock/sp-sp7021.h>
+
+#define REG(i) (pll_regs + (i) * 4)
+
+#define PLLA_CTL REG(7)
+#define PLLE_CTL REG(12)
+#define PLLF_CTL REG(13)
+#define PLLTV_CTL REG(14)
+#define PLLSYS_CTL REG(26)
+
+/* speical div_width values for PLLTV/PLLA */
+#define DIV_TV 33
+#define DIV_A 34
+
+/* PLLTV parameters */
+enum {
+ SEL_FRA,
+ SDM_MOD,
+ PH_SEL,
+ NFRA,
+ DIVR,
+ DIVN,
+ DIVM,
+ P_MAX
+};
+
+#define MASK_SEL_FRA GENMASK(1, 1)
+#define MASK_SDM_MOD GENMASK(2, 2)
+#define MASK_PH_SEL GENMASK(4, 4)
+#define MASK_NFRA GENMASK(12, 6)
+#define MASK_DIVR GENMASK(8, 7)
+#define MASK_DIVN GENMASK(7, 0)
+#define MASK_DIVM GENMASK(14, 8)
+
+/* HIWORD_MASK FIELD_PREP */
+#define HWM_FIELD_PREP(mask, value) \
+({ \
+ u32 m = mask; \
+ (m << 16) | FIELD_PREP(m, value); \
+})
+
+struct sp_pll {
+ struct clk_hw hw;
+ void __iomem *reg;
+ spinlock_t *lock; /* lock for reg */
+ int div_shift;
+ int div_width;
+ int pd_bit; /* power down bit idx */
+ int bp_bit; /* bypass bit idx */
+ unsigned long brate; /* base rate, TODO: replace brate with muldiv */
+ u32 p[P_MAX]; /* for hold PLLTV/PLLA parameters */
+};
+
+#define to_sp_pll(_hw) container_of(_hw, struct sp_pll, hw)
+
+#define clk_regs (sp_clk_base + 0x000) /* G0 ~ CLKEN */
+#define pll_regs (sp_clk_base + 0x200) /* G4 ~ PLL */
+static void __iomem *sp_clk_base;
+static struct clk_hw_onecell_data *sp_clk_data;
+
+#define F_CRITICAL BIT(31) /* clock is critical */
+#define F_EXTCLK BIT(16) /* parent clock is EXTCLK */
+const struct clk_parent_data parents[] = {
+ { .name = "pllsys" },
+ { .name = "extclk" },
+ { .name = "plle" },
+};
+
+#define PD_PLLSYS (&parents[0])
+#define PD_EXTCLK (&parents[1])
+#define PD_PLLE (&parents[2])
+
+static const u32 gates[] = {
+ CLK_SYSTEM | F_CRITICAL,
+ CLK_RTC,
+ CLK_IOCTL | F_CRITICAL,
+ CLK_IOP | F_CRITICAL,
+ CLK_OTPRX,
+ CLK_NOC,
+ CLK_BR,
+ CLK_RBUS_L00 | F_CRITICAL,
+ CLK_SPIFL,
+ CLK_SDCTRL0 | F_CRITICAL,
+ CLK_PERI0 | F_EXTCLK,
+ CLK_A926 | F_CRITICAL,
+ CLK_UMCTL2 | F_CRITICAL,
+ CLK_PERI1 | F_EXTCLK,
+
+ CLK_DDR_PHY0 | F_CRITICAL,
+ CLK_ACHIP | F_CRITICAL,
+ CLK_STC0,
+ CLK_STC_AV0,
+ CLK_STC_AV1,
+ CLK_STC_AV2,
+ CLK_UA0 | F_EXTCLK,
+ CLK_UA1 | F_EXTCLK,
+ CLK_UA2 | F_EXTCLK,
+ CLK_UA3 | F_EXTCLK,
+ CLK_UA4 | F_EXTCLK,
+ CLK_HWUA | F_EXTCLK,
+ CLK_DDC0,
+ CLK_UADMA | F_EXTCLK,
+
+ CLK_CBDMA0,
+ CLK_CBDMA1,
+ CLK_SPI_COMBO_0,
+ CLK_SPI_COMBO_1,
+ CLK_SPI_COMBO_2,
+ CLK_SPI_COMBO_3,
+ CLK_AUD,
+ CLK_USBC0,
+ CLK_USBC1,
+ CLK_UPHY0,
+ CLK_UPHY1,
+
+ CLK_I2CM0,
+ CLK_I2CM1,
+ CLK_I2CM2,
+ CLK_I2CM3,
+ CLK_PMC,
+ CLK_CARD_CTL0,
+ CLK_CARD_CTL1,
+
+ CLK_CARD_CTL4,
+ CLK_BCH,
+ CLK_DDFCH,
+ CLK_CSIIW0,
+ CLK_CSIIW1,
+ CLK_MIPICSI0,
+ CLK_MIPICSI1,
+
+ CLK_HDMI_TX,
+ CLK_VPOST,
+
+ CLK_TGEN,
+ CLK_DMIX,
+ CLK_TCON,
+ CLK_INTERRUPT | F_CRITICAL,
+
+ CLK_RGST | F_CRITICAL,
+ CLK_GPIO,
+ CLK_RBUS_TOP | F_CRITICAL,
+
+ CLK_MAILBOX,
+ CLK_SPIND,
+ CLK_I2C2CBUS,
+ CLK_SEC,
+ CLK_GPOST0,
+ CLK_DVE,
+
+ CLK_OSD0,
+ CLK_DISP_PWM,
+ CLK_UADBG,
+ CLK_DUMMY_MASTER,
+ CLK_FIO_CTL,
+ CLK_FPGA,
+ CLK_L2SW,
+ CLK_ICM,
+ CLK_AXI_GLOBAL,
+};
+
+static DEFINE_SPINLOCK(plla_lock);
+static DEFINE_SPINLOCK(plle_lock);
+static DEFINE_SPINLOCK(pllf_lock);
+static DEFINE_SPINLOCK(pllsys_lock);
+static DEFINE_SPINLOCK(plltv_lock);
+
+#define _M 1000000UL
+#define F_27M (27 * _M)
+
+/*********************************** PLL_TV **********************************/
+
+/* TODO: set proper FVCO range */
+#define FVCO_MIN (100 * _M)
+#define FVCO_MAX (200 * _M)
+
+#define F_MIN (FVCO_MIN / 8)
+#define F_MAX (FVCO_MAX)
+
+static long plltv_integer_div(struct sp_pll *clk, unsigned long freq)
+{
+ /* valid m values: 27M must be divisible by m, 0 means end */
+ static const u32 m_table[] = {
+ 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 0
+ };
+ u32 m, n, r;
+ unsigned long fvco, nf;
+
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("%s: %s freq:%lu < F_MIN:%lu, round up\n",
+ __func__, clk_hw_get_name(&clk->hw), freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("%s: %s freq:%lu > F_MAX:%lu, round down\n",
+ __func__, clk_hw_get_name(&clk->hw), freq, F_MAX);
+ freq = F_MAX;
+ }
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+
+ /* DIVM */
+ for (m = 0; m_table[m]; m++) {
+ nf = fvco * m_table[m];
+ n = nf / F_27M;
+ if ((n * F_27M) == nf)
+ break;
+ }
+ m = m_table[m];
+
+ if (!m) {
+ pr_err("%s: %s freq:%lu not found a valid setting\n",
+ __func__, clk_hw_get_name(&clk->hw), freq);
+ return -EINVAL;
+ }
+
+ /* save parameters */
+ clk->p[SEL_FRA] = 0;
+ clk->p[DIVR] = r;
+ clk->p[DIVN] = n;
+ clk->p[DIVM] = m;
+
+ return freq;
+}
+
+/* parameters for PLLTV fractional divider */
+static const u32 pt[][5] = {
+ /* conventional fractional */
+ {
+ 1, // factor
+ 5, // 5 * p0 (nint)
+ 1, // 1 * p0
+ F_27M, // F_27M / p0
+ 1, // p0 / p2
+ },
+ /* phase rotation */
+ {
+ 10, // factor
+ 54, // 5.4 * p0 (nint)
+ 2, // 0.2 * p0
+ F_27M / 10, // F_27M / p0
+ 5, // p0 / p2
+ },
+};
+
+static const u32 mods[] = { 91, 55 }; /* SDM_MOD mod values */
+
+static long plltv_fractional_div(struct sp_pll *clk, unsigned long freq)
+{
+ u32 m, r;
+ u32 nint, nfra;
+ u32 df_quotient_min = 210000000;
+ u32 df_remainder_min = 0;
+ unsigned long fvco, nf, f, fout = 0;
+ int sdm, ph;
+
+ /* check freq */
+ if (freq < F_MIN) {
+ pr_warn("%s: %s freq:%lu < F_MIN:%lu, round up\n",
+ __func__, clk_hw_get_name(&clk->hw), freq, F_MIN);
+ freq = F_MIN;
+ } else if (freq > F_MAX) {
+ pr_warn("%s: %s freq:%lu > F_MAX:%lu, round down\n",
+ __func__, clk_hw_get_name(&clk->hw), freq, F_MAX);
+ freq = F_MAX;
+ }
+
+ /* DIVR 0~3 */
+ for (r = 0; r <= 3; r++) {
+ fvco = freq << r;
+ if (fvco <= FVCO_MAX)
+ break;
+ }
+ f = F_27M >> r;
+
+ /* PH_SEL 1/0 */
+ for (ph = 1; ph >= 0; ph--) {
+ const u32 *pp = pt[ph];
+ u32 ms = 1;
+
+ /* SDM_MOD 0/1 */
+ for (sdm = 0; sdm <= 1; sdm++) {
+ u32 mod = mods[sdm];
+
+ /* DIVM 1~32 */
+ for (m = ms; m <= 32; m++) {
+ u32 df; /* diff freq */
+ u32 df_quotient = 0, df_remainder = 0;
+
+ nf = fvco * m;
+ nint = nf / pp[3];
+
+ if (nint < pp[1])
+ continue;
+ if (nint > pp[1])
+ break;
+
+ nfra = (((nf % pp[3]) * mod * pp[4]) + (F_27M / 2)) / F_27M;
+ if (nfra)
+ df = (f * (nint + pp[2]) / pp[0]) -
+ (f * (mod - nfra) / mod / pp[4]);
+ else
+ df = (f * (nint) / pp[0]);
+
+ df_quotient = df / m;
+ df_remainder = ((df % m) * 1000) / m;
+
+ if (freq > df_quotient) {
+ df_quotient = freq - df_quotient - 1;
+ df_remainder = 1000 - df_remainder;
+ } else {
+ df_quotient = df_quotient - freq;
+ }
+
+ if (df_quotient_min > df_quotient ||
+ (df_quotient_min == df_quotient &&
+ df_remainder_min > df_remainder)) {
+ /* found a closer freq, save parameters */
+ clk->p[SEL_FRA] = 1;
+ clk->p[SDM_MOD] = sdm;
+ clk->p[PH_SEL] = ph;
+ clk->p[NFRA] = nfra;
+ clk->p[DIVR] = r;
+ clk->p[DIVM] = m;
+
+ fout = df / m;
+ df_quotient_min = df_quotient;
+ df_remainder_min = df_remainder;
+ }
+ }
+ }
+ }
+
+ if (!fout) {
+ pr_err("%s: %s freq:%lu not found a valid setting\n",
+ __func__, clk_hw_get_name(&clk->hw), freq);
+ return -EINVAL;
+ }
+
+ return fout;
+}
+
+static long plltv_div(struct sp_pll *clk, unsigned long freq)
+{
+ if (freq % 100)
+ return plltv_fractional_div(clk, freq);
+
+ return plltv_integer_div(clk, freq);
+}
+
+static void plltv_set_rate(struct sp_pll *clk)
+{
+ u32 reg;
+
+ reg = HWM_FIELD_PREP(MASK_SEL_FRA, clk->p[SEL_FRA]);
+ reg |= HWM_FIELD_PREP(MASK_SDM_MOD, clk->p[SDM_MOD]);
+ reg |= HWM_FIELD_PREP(MASK_PH_SEL, clk->p[PH_SEL]);
+ reg |= HWM_FIELD_PREP(MASK_NFRA, clk->p[NFRA]);
+ writel(reg, clk->reg);
+
+ reg = HWM_FIELD_PREP(MASK_DIVR, clk->p[DIVR]);
+ writel(reg, clk->reg + 4);
+
+ reg = HWM_FIELD_PREP(MASK_DIVN, clk->p[DIVN] - 1);
+ reg |= HWM_FIELD_PREP(MASK_DIVM, clk->p[DIVM] - 1);
+ writel(reg, clk->reg + 8);
+}
+
+/*********************************** PLL_A ***********************************/
+
+/* from Q628_PLLs_REG_setting.xlsx */
+static const struct {
+ u32 rate;
+ u32 regs[5];
+} pa[] = {
+ {
+ .rate = 135475200,
+ .regs = {
+ 0x4801,
+ 0x02df,
+ 0x248f,
+ 0x0211,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 147456000,
+ .regs = {
+ 0x4801,
+ 0x1adf,
+ 0x2490,
+ 0x0349,
+ 0x33e9
+ }
+ },
+ {
+ .rate = 196608000,
+ .regs = {
+ 0x4801,
+ 0x42ef,
+ 0x2495,
+ 0x01c6,
+ 0x33e9
+ }
+ },
+};
+
+static void plla_set_rate(struct sp_pll *clk)
+{
+ const u32 *pp = pa[clk->p[0]].regs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pa->regs); i++)
+ writel(0xffff0000 | pp[i], clk->reg + (i * 4));
+}
+
+static long plla_round_rate(struct sp_pll *clk, unsigned long rate)
+{
+ int i = ARRAY_SIZE(pa);
+
+ while (--i) {
+ if (rate >= pa[i].rate)
+ break;
+ }
+ clk->p[0] = i;
+
+ return pa[i].rate;
+}
+
+/********************************** SP_PLL ***********************************/
+
+static long sp_pll_calc_div(struct sp_pll *clk, unsigned long rate)
+{
+ u32 fbdiv;
+ u32 max = 1 << clk->div_width;
+
+ fbdiv = DIV_ROUND_CLOSEST(rate, clk->brate);
+ if (fbdiv > max)
+ fbdiv = max;
+
+ return fbdiv;
+}
+
+static long sp_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ long ret;
+
+ if (rate == *prate)
+ ret = *prate; /* bypass */
+ else if (clk->div_width == DIV_A) {
+ ret = plla_round_rate(clk, rate);
+ } else if (clk->div_width == DIV_TV) {
+ ret = plltv_div(clk, rate);
+ if (ret < 0)
+ ret = *prate;
+ } else {
+ ret = sp_pll_calc_div(clk, rate) * clk->brate;
+ }
+
+ return ret;
+}
+
+static unsigned long sp_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ u32 reg = readl(clk->reg);
+ unsigned long ret;
+
+ if (reg & BIT(clk->bp_bit))
+ ret = prate; /* bypass */
+ else if (clk->div_width == DIV_A)
+ ret = pa[clk->p[0]].rate;
+ else if (clk->div_width == DIV_TV) {
+ u32 m, r, reg2;
+
+ r = FIELD_GET(MASK_DIVR, readl(clk->reg + 4));
+ reg2 = readl(clk->reg + 8);
+ m = FIELD_GET(MASK_DIVM, reg2) + 1;
+
+ if (reg & BIT(1)) { /* SEL_FRA */
+ /* fractional divider */
+ u32 sdm = FIELD_GET(MASK_SDM_MOD, reg);
+ u32 ph = FIELD_GET(MASK_PH_SEL, reg);
+ u32 nfra = FIELD_GET(MASK_NFRA, reg);
+ const u32 *pp = pt[ph];
+
+ ret = prate >> r;
+ ret = (ret * (pp[1] + pp[2]) / pp[0]) -
+ (ret * (mods[sdm] - nfra) / mods[sdm] / pp[4]);
+ ret /= m;
+ } else {
+ /* integer divider */
+ u32 n = FIELD_GET(MASK_DIVN, reg2) + 1;
+
+ ret = (prate / m * n) >> r;
+ }
+ } else {
+ u32 fbdiv = ((reg >> clk->div_shift) & ((1 << clk->div_width) - 1)) + 1;
+
+ ret = clk->brate * fbdiv;
+ }
+
+ return ret;
+}
+
+static int sp_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long prate)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(clk->lock, flags);
+
+ reg = BIT(clk->bp_bit + 16); /* HIWORD_MASK */
+
+ if (rate == prate)
+ reg |= BIT(clk->bp_bit); /* bypass */
+ else if (clk->div_width == DIV_A)
+ plla_set_rate(clk);
+ else if (clk->div_width == DIV_TV)
+ plltv_set_rate(clk);
+ else if (clk->div_width) {
+ u32 fbdiv = sp_pll_calc_div(clk, rate);
+ u32 mask = GENMASK(clk->div_shift + clk->div_width - 1, clk->div_shift);
+
+ reg |= (mask << 16) | (((fbdiv - 1) << clk->div_shift) & mask);
+ }
+
+ writel(reg, clk->reg);
+
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static int sp_pll_enable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(clk->lock, flags);
+ writel(BIT(clk->pd_bit + 16) | BIT(clk->pd_bit), clk->reg); /* power up */
+ spin_unlock_irqrestore(clk->lock, flags);
+
+ return 0;
+}
+
+static void sp_pll_disable(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(clk->lock, flags);
+ writel(BIT(clk->pd_bit + 16), clk->reg); /* power down */
+ spin_unlock_irqrestore(clk->lock, flags);
+}
+
+static int sp_pll_is_enabled(struct clk_hw *hw)
+{
+ struct sp_pll *clk = to_sp_pll(hw);
+
+ return readl(clk->reg) & BIT(clk->pd_bit);
+}
+
+static const struct clk_ops sp_pll_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .round_rate = sp_pll_round_rate,
+ .recalc_rate = sp_pll_recalc_rate,
+ .set_rate = sp_pll_set_rate
+};
+
+static const struct clk_ops sp_pll_sub_ops = {
+ .enable = sp_pll_enable,
+ .disable = sp_pll_disable,
+ .is_enabled = sp_pll_is_enabled,
+ .recalc_rate = sp_pll_recalc_rate,
+};
+
+struct clk_hw *sp_pll_register(struct device *dev, const char *name,
+ const struct clk_parent_data *parent_data,
+ void __iomem *reg, int pd_bit, int bp_bit,
+ unsigned long brate, int shift, int width,
+ spinlock_t *lock)
+{
+ struct sp_pll *pll;
+ struct clk_hw *hw;
+ struct clk_init_data initd = {
+ .name = name,
+ .parent_data = parent_data,
+ .ops = (bp_bit >= 0) ? &sp_pll_ops : &sp_pll_sub_ops,
+ .num_parents = 1,
+ /* system clock, should not be disabled */
+ .flags = (reg == PLLSYS_CTL) ? CLK_IS_CRITICAL : 0,
+ };
+ int ret;
+
+ pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->hw.init = &initd;
+ pll->reg = reg;
+ pll->pd_bit = pd_bit;
+ pll->bp_bit = bp_bit;
+ pll->brate = brate;
+ pll->div_shift = shift;
+ pll->div_width = width;
+ pll->lock = lock;
+
+ hw = &pll->hw;
+ ret = devm_clk_hw_register(dev, hw);
+ if (ret) {
+ kfree(pll);
+ hw = ERR_PTR(ret);
+ } else {
+ pr_debug("%-20s%lu\n", name, clk_hw_get_rate(hw));
+ devm_clk_hw_register_clkdev(dev, hw, NULL, name);
+ }
+
+ return hw;
+}
+
+static int sp7021_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int i, ret;
+ struct clk_hw **hws;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ sp_clk_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (WARN_ON(!sp_clk_base))
+ return -ENXIO;
+
+ sp_clk_data = devm_kzalloc(dev, struct_size(sp_clk_data, hws, CLK_MAX),
+ GFP_KERNEL);
+ if (!sp_clk_data)
+ return -ENOMEM;
+
+ hws = sp_clk_data->hws;
+
+ /* PLL_A */
+ hws[PLL_A] = sp_pll_register(dev, "plla", PD_EXTCLK, PLLA_CTL,
+ 11, 12, 27000000, 0, DIV_A, &plla_lock);
+ if (IS_ERR(hws[PLL_A]))
+ return PTR_ERR(hws[PLL_A]);
+
+ /* PLL_E */
+ hws[PLL_E] = sp_pll_register(dev, "plle", PD_EXTCLK, PLLE_CTL,
+ 6, 2, 50000000, 0, 0, &plle_lock);
+ if (IS_ERR(hws[PLL_E]))
+ return PTR_ERR(hws[PLL_E]);
+ hws[PLL_E_2P5] = sp_pll_register(dev, "plle_2p5", PD_PLLE, PLLE_CTL,
+ 13, -1, 2500000, 0, 0, &plle_lock);
+ if (IS_ERR(hws[PLL_E_2P5]))
+ return PTR_ERR(hws[PLL_E_2P5]);
+ hws[PLL_E_25] = sp_pll_register(dev, "plle_25", PD_PLLE, PLLE_CTL,
+ 12, -1, 25000000, 0, 0, &plle_lock);
+ if (IS_ERR(hws[PLL_E_25]))
+ return PTR_ERR(hws[PLL_E_25]);
+ hws[PLL_E_112P5] = sp_pll_register(dev, "plle_112p5", PD_PLLE, PLLE_CTL,
+ 11, -1, 112500000, 0, 0, &plle_lock);
+ if (IS_ERR(hws[PLL_E_112P5]))
+ return PTR_ERR(hws[PLL_E_112P5]);
+
+ /* PLL_F */
+ hws[PLL_F] = sp_pll_register(dev, "pllf", PD_EXTCLK, PLLF_CTL,
+ 0, 10, 13500000, 1, 4, &pllf_lock);
+ if (IS_ERR(hws[PLL_F]))
+ return PTR_ERR(hws[PLL_F]);
+
+ /* PLL_TV */
+ hws[PLL_TV] = sp_pll_register(dev, "plltv", PD_EXTCLK, PLLTV_CTL,
+ 0, 15, 27000000, 0, DIV_TV, &plltv_lock);
+ if (IS_ERR(hws[PLL_TV]))
+ return PTR_ERR(hws[PLL_TV]);
+ hws[PLL_TV_A] = devm_clk_hw_register_divider(dev, "plltv_a", "plltv", 0,
+ PLLTV_CTL + 4, 5, 1,
+ CLK_DIVIDER_POWER_OF_TWO,
+ &plltv_lock);
+ if (IS_ERR(hws[PLL_TV_A]))
+ return PTR_ERR(hws[PLL_TV_A]);
+ ret = devm_clk_hw_register_clkdev(dev, hws[PLL_TV_A], NULL, "plltv_a");
+ if (ret)
+ return ret;
+
+ /* PLL_SYS */
+ hws[PLL_SYS] = sp_pll_register(dev, "pllsys", PD_EXTCLK, PLLSYS_CTL,
+ 10, 9, 13500000, 0, 4, &pllsys_lock);
+ if (IS_ERR(hws[PLL_SYS]))
+ return PTR_ERR(hws[PLL_SYS]);
+
+ /* gates */
+ for (i = 0; i < ARRAY_SIZE(gates); i++) {
+ u32 f = gates[i];
+ const char *parent_name = parents[!!(f & F_EXTCLK)].name;
+ bool critical = f & F_CRITICAL;
+ int j = f & 0xffff;
+ char s[10];
+
+ sprintf(s, "clken%02x", j);
+ hws[j] = clk_hw_register_gate(dev, s, parent_name,
+ critical ? CLK_IS_CRITICAL : 0,
+ clk_regs + (j >> 4) * 4,
+ j & 0x0f,
+ CLK_GATE_HIWORD_MASK, NULL);
+ if (IS_ERR(hws[j]))
+ return PTR_ERR(hws[j]);
+ }
+
+ sp_clk_data->num = CLK_MAX;
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, sp_clk_data);
+}
+
+static const struct of_device_id sp7021_clk_dt_ids[] = {
+ { .compatible = "sunplus,sp7021-clkc", },
+ { }
+};
+
+static struct platform_driver sp7021_clk_driver = {
+ .probe = sp7021_clk_probe,
+ .driver = {
+ .name = "sp7021-clk",
+ .of_match_table = sp7021_clk_dt_ids,
+ },
+};
+builtin_platform_driver(sp7021_clk_driver);
--
2.33.1
^ permalink raw reply related
* [PATCH v8 05/10] dt-bindings: clock: Add bindings for SP7021 clock driver
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add documentation to describe Sunplus SP7021 clock driver bindings.
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
Add clocks & clock-names.
---
.../bindings/clock/sunplus,sp7021-clkc.yaml | 52 ++++++++
MAINTAINERS | 2 +
include/dt-bindings/clock/sp-sp7021.h | 112 ++++++++++++++++++
3 files changed, 166 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
create mode 100644 include/dt-bindings/clock/sp-sp7021.h
diff --git a/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml b/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
new file mode 100644
index 000000000..e6d098cf4
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/sunplus,sp7021-clkc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 SoC Clock Controller Binding
+
+maintainers:
+ - Qin Jian <qinjian@cqplus1.com>
+
+properties:
+ compatible:
+ const: sunplus,sp7021-clkc
+
+ "#clock-cells":
+ const: 1
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: External oscillator clock
+ - description: System clock
+
+ clock-names:
+ items:
+ - const: extclk
+ - const: pllsys
+
+required:
+ - compatible
+ - "#clock-cells"
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ clkc: clock-controller@9c000000 {
+ compatible = "sunplus,sp7021-clkc";
+ #clock-cells = <1>;
+ reg = <0x9c000000 0x280>;
+ clocks = <&extclk>, <&clkc PLL_SYS>;
+ clock-names = "extclk", "pllsys";
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 6caffd6d0..90ebb823f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2661,8 +2661,10 @@ L: linux-arm-kernel@lists.infradead.org (moderated for mon-subscribers)
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/reset/reset-sunplus.c
+F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
ARM/Synaptics SoC support
diff --git a/include/dt-bindings/clock/sp-sp7021.h b/include/dt-bindings/clock/sp-sp7021.h
new file mode 100644
index 000000000..45dac6de8
--- /dev/null
+++ b/include/dt-bindings/clock/sp-sp7021.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#ifndef _DT_BINDINGS_CLOCK_SUNPLUS_SP7021_H
+#define _DT_BINDINGS_CLOCK_SUNPLUS_SP7021_H
+
+#define XTAL 27000000
+
+/* plls */
+#define PLL_A 0
+#define PLL_E 1
+#define PLL_E_2P5 2
+#define PLL_E_25 3
+#define PLL_E_112P5 4
+#define PLL_F 5
+#define PLL_TV 6
+#define PLL_TV_A 7
+#define PLL_SYS 8
+
+/* gates: mo_clken0 ~ mo_clken9 */
+#define CLK_SYSTEM 0x10
+#define CLK_RTC 0x12
+#define CLK_IOCTL 0x13
+#define CLK_IOP 0x14
+#define CLK_OTPRX 0x15
+#define CLK_NOC 0x16
+#define CLK_BR 0x17
+#define CLK_RBUS_L00 0x18
+#define CLK_SPIFL 0x19
+#define CLK_SDCTRL0 0x1a
+#define CLK_PERI0 0x1b
+#define CLK_A926 0x1d
+#define CLK_UMCTL2 0x1e
+#define CLK_PERI1 0x1f
+
+#define CLK_DDR_PHY0 0x20
+#define CLK_ACHIP 0x22
+#define CLK_STC0 0x24
+#define CLK_STC_AV0 0x25
+#define CLK_STC_AV1 0x26
+#define CLK_STC_AV2 0x27
+#define CLK_UA0 0x28
+#define CLK_UA1 0x29
+#define CLK_UA2 0x2a
+#define CLK_UA3 0x2b
+#define CLK_UA4 0x2c
+#define CLK_HWUA 0x2d
+#define CLK_DDC0 0x2e
+#define CLK_UADMA 0x2f
+
+#define CLK_CBDMA0 0x30
+#define CLK_CBDMA1 0x31
+#define CLK_SPI_COMBO_0 0x32
+#define CLK_SPI_COMBO_1 0x33
+#define CLK_SPI_COMBO_2 0x34
+#define CLK_SPI_COMBO_3 0x35
+#define CLK_AUD 0x36
+#define CLK_USBC0 0x3a
+#define CLK_USBC1 0x3b
+#define CLK_UPHY0 0x3d
+#define CLK_UPHY1 0x3e
+
+#define CLK_I2CM0 0x40
+#define CLK_I2CM1 0x41
+#define CLK_I2CM2 0x42
+#define CLK_I2CM3 0x43
+#define CLK_PMC 0x4d
+#define CLK_CARD_CTL0 0x4e
+#define CLK_CARD_CTL1 0x4f
+
+#define CLK_CARD_CTL4 0x52
+#define CLK_BCH 0x54
+#define CLK_DDFCH 0x5b
+#define CLK_CSIIW0 0x5c
+#define CLK_CSIIW1 0x5d
+#define CLK_MIPICSI0 0x5e
+#define CLK_MIPICSI1 0x5f
+
+#define CLK_HDMI_TX 0x60
+#define CLK_VPOST 0x65
+
+#define CLK_TGEN 0x70
+#define CLK_DMIX 0x71
+#define CLK_TCON 0x7a
+#define CLK_INTERRUPT 0x7f
+
+#define CLK_RGST 0x80
+#define CLK_GPIO 0x83
+#define CLK_RBUS_TOP 0x84
+
+#define CLK_MAILBOX 0x96
+#define CLK_SPIND 0x9a
+#define CLK_I2C2CBUS 0x9b
+#define CLK_SEC 0x9d
+#define CLK_DVE 0x9e
+#define CLK_GPOST0 0x9f
+
+#define CLK_OSD0 0xa0
+#define CLK_DISP_PWM 0xa2
+#define CLK_UADBG 0xa3
+#define CLK_DUMMY_MASTER 0xa4
+#define CLK_FIO_CTL 0xa5
+#define CLK_FPGA 0xa6
+#define CLK_L2SW 0xa7
+#define CLK_ICM 0xa8
+#define CLK_AXI_GLOBAL 0xa9
+
+#define CLK_MAX 0xb0
+
+#endif
--
2.33.1
^ permalink raw reply related
* [PATCH v8 01/10] dt-bindings: vendor-prefixes: Add Sunplus
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian, Rob Herring
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add vendor prefix for Sunplus Technology Co., Ltd. (http://www.sunplus.com)
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index a867f7102..50d4ee5ac 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1131,6 +1131,8 @@ patternProperties:
description: Summit microelectronics
"^sunchip,.*":
description: Shenzhen Sunchip Technology Co., Ltd
+ "^sunplus,.*":
+ description: Sunplus Technology Co., Ltd.
"^SUNW,.*":
description: Sun Microsystems, Inc
"^supermicro,.*":
--
2.33.1
^ permalink raw reply related
* [PATCH v8 02/10] dt-bindings: arm: sunplus: Add bindings for Sunplus SP7021 SoC boards
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian, Rob Herring
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
This introduces bindings for boards based Sunplus SP7021 SoC.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
.../bindings/arm/sunplus,sp7021.yaml | 27 +++++++++++++++++++
MAINTAINERS | 7 +++++
2 files changed, 34 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
diff --git a/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml b/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
new file mode 100644
index 000000000..5b9985b73
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/sunplus,sp7021.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 Boards Device Tree Bindings
+
+maintainers:
+ - qinjian <qinjian@cqplus1.com>
+
+description: |
+ ARM platforms using Sunplus SP7021, an ARM Cortex A7 (4-cores) based SoC.
+ Wiki: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+
+properties:
+ $nodename:
+ const: '/'
+ compatible:
+ oneOf:
+ - items:
+ - const: sunplus,sp7021-achip
+
+additionalProperties: true
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e0bca0de0..6a5422f10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2655,6 +2655,13 @@ F: drivers/clocksource/armv7m_systick.c
N: stm32
N: stm
+ARM/SUNPLUS SP7021 SOC SUPPORT
+M: Qin Jian <qinjian@cqplus1.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for mon-subscribers)
+S: Maintained
+W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
+F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+
ARM/Synaptics SoC support
M: Jisheng Zhang <Jisheng.Zhang@synaptics.com>
M: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
--
2.33.1
^ permalink raw reply related
* [PATCH v8 07/10] dt-bindings: interrupt-controller: Add bindings for SP7021 interrupt controller
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian, Rob Herring
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add documentation to describe Sunplus SP7021 interrupt controller bindings.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
.../sunplus,sp7021-intc.yaml | 62 +++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 63 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
new file mode 100644
index 000000000..5daeab63c
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/sunplus,sp7021-intc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SP7021 SoC Interrupt Controller Device Tree Bindings
+
+maintainers:
+ - Qin Jian <qinjian@cqplus1.com>
+
+properties:
+ compatible:
+ items:
+ - const: sunplus,sp7021-intc
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 2
+ description:
+ The first cell is the IRQ number, the second cell is the trigger
+ type as defined in interrupt.txt in this directory.
+
+ reg:
+ maxItems: 2
+ description:
+ Specifies base physical address(s) and size of the controller regs.
+ The 1st region include type/polarity/priority/mask regs.
+ The 2nd region include clear/masked_ext0/masked_ext1/group regs.
+
+ interrupts:
+ maxItems: 2
+ description:
+ EXT_INT0 & EXT_INT1, 2 interrupts references to primary interrupt
+ controller.
+
+required:
+ - compatible
+ - interrupt-controller
+ - "#interrupt-cells"
+ - reg
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ intc: interrupt-controller@9c000780 {
+ compatible = "sunplus,sp7021-intc";
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ reg = <0x9c000780 0x80>, <0x9c000a80 0x80>;
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>, /* EXT_INT0 */
+ <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>; /* EXT_INT1 */
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 5069f552f..6b3bbe021 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2662,6 +2662,7 @@ S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
+F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/clk/clk-sp7021.c
F: drivers/reset/reset-sunplus.c
--
2.33.1
^ permalink raw reply related
* [PATCH v8 03/10] dt-bindings: reset: Add bindings for SP7021 reset driver
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian, Rob Herring
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add documentation to describe Sunplus SP7021 reset driver bindings.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
.../bindings/reset/sunplus,reset.yaml | 38 ++++++++
MAINTAINERS | 2 +
include/dt-bindings/reset/sp-sp7021.h | 97 +++++++++++++++++++
3 files changed, 137 insertions(+)
create mode 100644 Documentation/devicetree/bindings/reset/sunplus,reset.yaml
create mode 100644 include/dt-bindings/reset/sp-sp7021.h
diff --git a/Documentation/devicetree/bindings/reset/sunplus,reset.yaml b/Documentation/devicetree/bindings/reset/sunplus,reset.yaml
new file mode 100644
index 000000000..c083c821f
--- /dev/null
+++ b/Documentation/devicetree/bindings/reset/sunplus,reset.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/reset/sunplus,reset.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Sunplus SoC Reset Controller
+
+maintainers:
+ - Qin Jian <qinjian@cqplus1.com>
+
+properties:
+ compatible:
+ const: sunplus,sp7021-reset
+
+ "#reset-cells":
+ const: 1
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - "#reset-cells"
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ rstc: reset@9c000054 {
+ compatible = "sunplus,sp7021-reset";
+ #reset-cells = <1>;
+ reg = <0x9c000054 0x28>;
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 6a5422f10..652f42cab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2661,6 +2661,8 @@ L: linux-arm-kernel@lists.infradead.org (moderated for mon-subscribers)
S: Maintained
W: https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml
+F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
+F: include/dt-bindings/reset/sp-sp7021.h
ARM/Synaptics SoC support
M: Jisheng Zhang <Jisheng.Zhang@synaptics.com>
diff --git a/include/dt-bindings/reset/sp-sp7021.h b/include/dt-bindings/reset/sp-sp7021.h
new file mode 100644
index 000000000..fd2a50327
--- /dev/null
+++ b/include/dt-bindings/reset/sp-sp7021.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#ifndef _DT_BINDINGS_RST_SUNPLUS_SP7021_H
+#define _DT_BINDINGS_RST_SUNPLUS_SP7021_H
+
+/* mo_reset0 ~ mo_reset9 */
+#define RST_SYSTEM 0x00
+#define RST_RTC 0x02
+#define RST_IOCTL 0x03
+#define RST_IOP 0x04
+#define RST_OTPRX 0x05
+#define RST_NOC 0x06
+#define RST_BR 0x07
+#define RST_RBUS_L00 0x08
+#define RST_SPIFL 0x09
+#define RST_SDCTRL0 0x0a
+#define RST_PERI0 0x0b
+#define RST_A926 0x0d
+#define RST_UMCTL2 0x0e
+#define RST_PERI1 0x0f
+
+#define RST_DDR_PHY0 0x10
+#define RST_ACHIP 0x12
+#define RST_STC0 0x14
+#define RST_STC_AV0 0x15
+#define RST_STC_AV1 0x16
+#define RST_STC_AV2 0x17
+#define RST_UA0 0x18
+#define RST_UA1 0x19
+#define RST_UA2 0x1a
+#define RST_UA3 0x1b
+#define RST_UA4 0x1c
+#define RST_HWUA 0x1d
+#define RST_DDC0 0x1e
+#define RST_UADMA 0x1f
+
+#define RST_CBDMA0 0x20
+#define RST_CBDMA1 0x21
+#define RST_SPI_COMBO_0 0x22
+#define RST_SPI_COMBO_1 0x23
+#define RST_SPI_COMBO_2 0x24
+#define RST_SPI_COMBO_3 0x25
+#define RST_AUD 0x26
+#define RST_USBC0 0x2a
+#define RST_USBC1 0x2b
+#define RST_UPHY0 0x2d
+#define RST_UPHY1 0x2e
+
+#define RST_I2CM0 0x30
+#define RST_I2CM1 0x31
+#define RST_I2CM2 0x32
+#define RST_I2CM3 0x33
+#define RST_PMC 0x3d
+#define RST_CARD_CTL0 0x3e
+#define RST_CARD_CTL1 0x3f
+
+#define RST_CARD_CTL4 0x42
+#define RST_BCH 0x44
+#define RST_DDFCH 0x4b
+#define RST_CSIIW0 0x4c
+#define RST_CSIIW1 0x4d
+#define RST_MIPICSI0 0x4e
+#define RST_MIPICSI1 0x4f
+
+#define RST_HDMI_TX 0x50
+#define RST_VPOST 0x55
+
+#define RST_TGEN 0x60
+#define RST_DMIX 0x61
+#define RST_TCON 0x6a
+#define RST_INTERRUPT 0x6f
+
+#define RST_RGST 0x70
+#define RST_GPIO 0x73
+#define RST_RBUS_TOP 0x74
+
+#define RST_MAILBOX 0x86
+#define RST_SPIND 0x8a
+#define RST_I2C2CBUS 0x8b
+#define RST_SEC 0x8d
+#define RST_DVE 0x8e
+#define RST_GPOST0 0x8f
+
+#define RST_OSD0 0x90
+#define RST_DISP_PWM 0x92
+#define RST_UADBG 0x93
+#define RST_DUMMY_MASTER 0x94
+#define RST_FIO_CTL 0x95
+#define RST_FPGA 0x96
+#define RST_L2SW 0x97
+#define RST_ICM 0x98
+#define RST_AXI_GLOBAL 0x99
+
+#endif
--
2.33.1
^ permalink raw reply related
* [PATCH v8 08/10] irqchip: Add Sunplus SP7021 interrupt controller driver
From: Qin Jian @ 2022-01-21 7:53 UTC (permalink / raw)
To: robh+dt
Cc: mturquette, sboyd, tglx, maz, p.zabel, linux, broonie, arnd,
stefan.wahren, linux-arm-kernel, devicetree, linux-kernel,
linux-clk, wells.lu, Qin Jian
In-Reply-To: <cover.1642751015.git.qinjian@cqplus1.com>
Add interrupt controller driver for Sunplus SP7021 SoC.
This is the interrupt controller in P-chip which collects all interrupt
sources in P-chip and routes them to parent interrupt controller in C-chip.
Signed-off-by: Qin Jian <qinjian@cqplus1.com>
---
Fix the comments from Marc.
---
MAINTAINERS | 1 +
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-sp7021-intc.c | 288 ++++++++++++++++++++++++++++++
4 files changed, 299 insertions(+)
create mode 100644 drivers/irqchip/irq-sp7021-intc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6b3bbe021..febbd97bf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2665,6 +2665,7 @@ F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml
F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml
F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml
F: drivers/clk/clk-sp7021.c
+F: drivers/irqchip/irq-sp7021-intc.c
F: drivers/reset/reset-sunplus.c
F: include/dt-bindings/clock/sp-sp7021.h
F: include/dt-bindings/reset/sp-sp7021.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index aca7b595c..32d7a8e0c 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -602,4 +602,13 @@ config APPLE_AIC
Support for the Apple Interrupt Controller found on Apple Silicon SoCs,
such as the M1.
+config SUNPLUS_SP7021_INTC
+ bool "Sunplus SP7021 interrupt controller" if COMPILE_TEST
+ default SOC_SP7021
+ help
+ Support for the Sunplus SP7021 Interrupt Controller IP core.
+ SP7021 SoC has 2 Chips: C-Chip & P-Chip. This is used as a
+ chained controller, routing all interrupt source in P-Chip to
+ the primary controller on C-Chip.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index f88cbf36a..75411f654 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -116,3 +116,4 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o
obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o
obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
+obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o
diff --git a/drivers/irqchip/irq-sp7021-intc.c b/drivers/irqchip/irq-sp7021-intc.c
new file mode 100644
index 000000000..d1821a955
--- /dev/null
+++ b/drivers/irqchip/irq-sp7021-intc.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * Copyright (C) Sunplus Technology Co., Ltd.
+ * All rights reserved.
+ */
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#define SP_INTC_HWIRQ_MIN 0
+#define SP_INTC_HWIRQ_MAX 223
+
+#define SP_INTC_NR_IRQS (SP_INTC_HWIRQ_MAX - SP_INTC_HWIRQ_MIN + 1)
+#define SP_INTC_NR_GROUPS DIV_ROUND_UP(SP_INTC_NR_IRQS, 32)
+#define SP_INTC_REG_SIZE (SP_INTC_NR_GROUPS * 4)
+
+/* REG_GROUP_0 regs */
+#define REG_INTR_TYPE (sp_intc.g0)
+#define REG_INTR_POLARITY (REG_INTR_TYPE + SP_INTC_REG_SIZE)
+#define REG_INTR_PRIORITY (REG_INTR_POLARITY + SP_INTC_REG_SIZE)
+#define REG_INTR_MASK (REG_INTR_PRIORITY + SP_INTC_REG_SIZE)
+
+/* REG_GROUP_1 regs */
+#define REG_INTR_CLEAR (sp_intc.g1)
+#define REG_MASKED_EXT1 (REG_INTR_CLEAR + SP_INTC_REG_SIZE)
+#define REG_MASKED_EXT0 (REG_MASKED_EXT1 + SP_INTC_REG_SIZE)
+#define REG_INTR_GROUP (REG_INTR_CLEAR + 31 * 4)
+
+#define GROUP_MASK (BIT(SP_INTC_NR_GROUPS) - 1)
+#define GROUP_SHIFT_EXT1 (0)
+#define GROUP_SHIFT_EXT0 (8)
+
+/*
+ * When GPIO_INT0~7 set to edge trigger, doesn't work properly.
+ * WORKAROUND: change it to level trigger, and toggle the polarity
+ * at ACK/Handler to make the HW work.
+ */
+#define GPIO_INT0_HWIRQ 120
+#define GPIO_INT7_HWIRQ 127
+#define IS_GPIO_INT(irq) \
+({ \
+ u32 i = irq; \
+ (i >= GPIO_INT0_HWIRQ) && (i <= GPIO_INT7_HWIRQ); \
+})
+
+/* index of states */
+enum {
+ _IS_EDGE = 0,
+ _IS_LOW,
+ _IS_ACTIVE
+};
+
+#define STATE_BIT(irq, idx) (((irq) - GPIO_INT0_HWIRQ) * 3 + (idx))
+#define ASSIGN_STATE(irq, idx, v) assign_bit(STATE_BIT(irq, idx), sp_intc.states, v)
+#define TEST_STATE(irq, idx) test_bit(STATE_BIT(irq, idx), sp_intc.states)
+
+static struct sp_intctl {
+ /*
+ * REG_GROUP_0: include type/polarity/priority/mask regs.
+ * REG_GROUP_1: include clear/masked_ext0/masked_ext1/group regs.
+ */
+ void __iomem *g0; // REG_GROUP_0 base
+ void __iomem *g1; // REG_GROUP_1 base
+
+ struct irq_domain *domain;
+ raw_spinlock_t lock;
+
+ /*
+ * store GPIO_INT states
+ * each interrupt has 3 states: is_edge, is_low, is_active
+ */
+ DECLARE_BITMAP(states, (GPIO_INT7_HWIRQ - GPIO_INT0_HWIRQ + 1) * 3);
+} sp_intc;
+
+static struct irq_chip sp_intc_chip;
+
+static void sp_intc_assign_bit(u32 hwirq, void __iomem *base, bool value)
+{
+ u32 offset, mask;
+ unsigned long flags;
+ void __iomem *reg;
+
+ offset = (hwirq / 32) * 4;
+ reg = base + offset;
+
+ raw_spin_lock_irqsave(&sp_intc.lock, flags);
+ mask = readl_relaxed(reg);
+ if (value)
+ mask |= BIT(hwirq % 32);
+ else
+ mask &= ~BIT(hwirq % 32);
+ writel_relaxed(mask, reg);
+ raw_spin_unlock_irqrestore(&sp_intc.lock, flags);
+}
+
+static void sp_intc_ack_irq(struct irq_data *d)
+{
+ u32 hwirq = d->hwirq;
+
+ if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_EDGE))) { // WORKAROUND
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, !TEST_STATE(hwirq, _IS_LOW));
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, true);
+ }
+
+ sp_intc_assign_bit(hwirq, REG_INTR_CLEAR, 1);
+}
+
+static void sp_intc_mask_irq(struct irq_data *d)
+{
+ sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 0);
+}
+
+static void sp_intc_unmask_irq(struct irq_data *d)
+{
+ sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 1);
+}
+
+static int sp_intc_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 hwirq = d->hwirq;
+ bool is_edge = !(type & IRQ_TYPE_LEVEL_MASK);
+ bool is_low = (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING);
+
+ irq_set_handler_locked(d, is_edge ? handle_edge_irq : handle_level_irq);
+
+ if (unlikely(IS_GPIO_INT(hwirq) && is_edge)) { // WORKAROUND
+ /* store states */
+ ASSIGN_STATE(hwirq, _IS_EDGE, is_edge);
+ ASSIGN_STATE(hwirq, _IS_LOW, is_low);
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
+ /* change to level */
+ is_edge = false;
+ }
+
+ sp_intc_assign_bit(hwirq, REG_INTR_TYPE, is_edge);
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, is_low);
+
+ return 0;
+}
+
+static int sp_intc_get_ext_irq(int ext_num)
+{
+ void __iomem *base = ext_num ? REG_MASKED_EXT1 : REG_MASKED_EXT0;
+ u32 shift = ext_num ? GROUP_SHIFT_EXT1 : GROUP_SHIFT_EXT0;
+ u32 groups;
+ u32 pending_group;
+ u32 group;
+ u32 pending_irq;
+
+ groups = readl_relaxed(REG_INTR_GROUP);
+ pending_group = (groups >> shift) & GROUP_MASK;
+ if (!pending_group)
+ return -1;
+
+ group = fls(pending_group) - 1;
+ pending_irq = readl_relaxed(base + group * 4);
+ if (!pending_irq)
+ return -1;
+
+ return (group * 32) + fls(pending_irq) - 1;
+}
+
+static void sp_intc_handle_ext_cascaded(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int ext_num = (int)irq_desc_get_handler_data(desc);
+ int hwirq;
+
+ chained_irq_enter(chip, desc);
+
+ while ((hwirq = sp_intc_get_ext_irq(ext_num)) >= 0) {
+ if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_ACTIVE))) { // WORKAROUND
+ ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
+ sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, TEST_STATE(hwirq, _IS_LOW));
+ } else {
+ generic_handle_domain_irq(sp_intc.domain, hwirq);
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+#ifdef CONFIG_SMP
+static int sp_intc_set_affinity(struct irq_data *d, const struct cpumask *mask, bool force)
+{
+ return -EINVAL;
+}
+#endif
+
+static struct irq_chip sp_intc_chip = {
+ .name = "sp_intc",
+ .irq_ack = sp_intc_ack_irq,
+ .irq_mask = sp_intc_mask_irq,
+ .irq_unmask = sp_intc_unmask_irq,
+ .irq_set_type = sp_intc_set_type,
+#ifdef CONFIG_SMP
+ .irq_set_affinity = sp_intc_set_affinity,
+#endif
+};
+
+static int sp_intc_irq_domain_map(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq);
+ irq_set_chip_data(irq, &sp_intc_chip);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops sp_intc_dm_ops = {
+ .xlate = irq_domain_xlate_twocell,
+ .map = sp_intc_irq_domain_map,
+};
+
+static int sp_intc_irq_map(struct device_node *node, int i)
+{
+ unsigned int irq;
+
+ irq = irq_of_parse_and_map(node, i);
+ if (!irq)
+ return -ENOENT;
+
+ irq_set_chained_handler_and_data(irq, sp_intc_handle_ext_cascaded, (void *)i);
+
+ return 0;
+}
+
+static int __init sp_intc_init_dt(struct device_node *node, struct device_node *parent)
+{
+ int i, ret;
+
+ sp_intc.g0 = of_iomap(node, 0);
+ if (!sp_intc.g0)
+ return -ENXIO;
+
+ sp_intc.g1 = of_iomap(node, 1);
+ if (!sp_intc.g1) {
+ ret = -ENXIO;
+ goto out_unmap0;
+ }
+
+ ret = sp_intc_irq_map(node, 0); // EXT_INT0
+ if (ret)
+ goto out_unmap1;
+
+ ret = sp_intc_irq_map(node, 1); // EXT_INT1
+ if (ret)
+ goto out_unmap1;
+
+ /* initial regs */
+ for (i = 0; i < SP_INTC_NR_GROUPS; i++) {
+ /* all mask */
+ writel_relaxed(0, REG_INTR_MASK + i * 4);
+ /* all edge */
+ writel_relaxed(~0, REG_INTR_TYPE + i * 4);
+ /* all high-active */
+ writel_relaxed(0, REG_INTR_POLARITY + i * 4);
+ /* all EXT_INT0 */
+ writel_relaxed(~0, REG_INTR_PRIORITY + i * 4);
+ /* all clear */
+ writel_relaxed(~0, REG_INTR_CLEAR + i * 4);
+ }
+
+ sp_intc.domain = irq_domain_add_linear(node, SP_INTC_NR_IRQS,
+ &sp_intc_dm_ops, &sp_intc);
+ if (!sp_intc.domain) {
+ ret = -ENOMEM;
+ goto out_unmap1;
+ }
+
+ raw_spin_lock_init(&sp_intc.lock);
+
+ return 0;
+
+out_unmap1:
+ iounmap(sp_intc.g1);
+out_unmap0:
+ iounmap(sp_intc.g0);
+
+ return ret;
+}
+
+IRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt);
--
2.33.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox