From: Jacky Huang <[email protected]>
This patchset adds initial support for the Nuvoton ma35d1 SoC, including
initial device tree, clock driver, reset driver, and serial driver.
This patchset cover letter is based from the initial support for Nuvoton
ma35d1 to keep tracking the version history.
This patchset had been applied to Linux kernel 6.3-rc2 and tested on the
Nuvoton ma35d1 SOM evaluation board.
(ma35d1 information: https://www.nuvoton.com/products/microprocessors/arm-cortex-a35-mpus/)
MA35D1 porting on linux-5.10.y can be found at: https://github.com/OpenNuvoton/MPU-Family
Jacky Huang (15):
arm64: Kconfig.platforms: Add config for Nuvoton MA35 platform
arm64: defconfig: Add Nuvoton MA35 family support
mfd: Add the header file of Nuvoton ma35d1 system manager
dt-bindings: clock: nuvoton: add binding for ma35d1 clock controller
dt-bindings: reset: nuvoton: add binding for ma35d1 IP reset control
dt-bindings: mfd: syscon: Add nuvoton,ma35d1-sys compatible
dt-bindings: arm: Add initial bindings for Nuvoton platform
dt-bindings: clock: Document ma35d1 clock controller bindings
dt-bindings: reset: Document ma35d1 reset controller bindings
dt-bindings: serial: Document ma35d1 uart controller bindings
arm64: dts: nuvoton: Add initial ma35d1 device tree
clk: nuvoton: Add clock driver for ma35d1 clock controller
reset: Add Nuvoton ma35d1 reset driver support
tty: serial: Add Nuvoton ma35d1 serial driver support
MAINTAINERS: Add entry for NUVOTON MA35
.../devicetree/bindings/arm/nuvoton.yaml | 30 +
.../bindings/clock/nuvoton,ma35d1-clk.yaml | 83 ++
.../devicetree/bindings/mfd/syscon.yaml | 1 +
.../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +
.../serial/nuvoton,ma35d1-serial.yaml | 52 +
MAINTAINERS | 12 +
arch/arm64/Kconfig.platforms | 9 +
arch/arm64/boot/dts/nuvoton/Makefile | 2 +
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 24 +
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 23 +
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 272 +++++
arch/arm64/configs/defconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/nuvoton/Makefile | 4 +
drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 +++
drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 ++++++++++
drivers/clk/nuvoton/clk-ma35d1.c | 970 ++++++++++++++++++
drivers/clk/nuvoton/clk-ma35d1.h | 198 ++++
drivers/reset/Kconfig | 6 +
drivers/reset/Makefile | 1 +
drivers/reset/reset-ma35d1.c | 152 +++
drivers/tty/serial/Kconfig | 18 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++
drivers/tty/serial/ma35d1_serial.h | 93 ++
.../dt-bindings/clock/nuvoton,ma35d1-clk.h | 253 +++++
.../dt-bindings/reset/nuvoton,ma35d1-reset.h | 108 ++
include/linux/mfd/ma35d1-sys.h | 95 ++
include/uapi/linux/serial_core.h | 3 +
29 files changed, 3982 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/nuvoton.yaml
create mode 100644 Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
create mode 100644 Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
create mode 100644 drivers/clk/nuvoton/Makefile
create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
create mode 100644 drivers/reset/reset-ma35d1.c
create mode 100644 drivers/tty/serial/ma35d1_serial.c
create mode 100644 drivers/tty/serial/ma35d1_serial.h
create mode 100644 include/dt-bindings/clock/nuvoton,ma35d1-clk.h
create mode 100644 include/dt-bindings/reset/nuvoton,ma35d1-reset.h
create mode 100644 include/linux/mfd/ma35d1-sys.h
--
2.34.1
From: Jacky Huang <[email protected]>
Enable basic drivers for ma35d1 booting up support: architecture,
device tree, clock, reset, and uart.
Signed-off-by: Jacky Huang <[email protected]>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 7790ee42c68a..c96189acb02c 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -53,6 +53,7 @@ CONFIG_ARCH_LAYERSCAPE=y
CONFIG_ARCH_MXC=y
CONFIG_ARCH_S32=y
CONFIG_ARCH_NPCM=y
+CONFIG_ARCH_NUVOTON=y
CONFIG_ARCH_QCOM=y
CONFIG_ARCH_RENESAS=y
CONFIG_ARCH_ROCKCHIP=y
--
2.34.1
From: Jacky Huang <[email protected]>
The system manager is a set of registers used for power control,
multi-function pin control, USB phy control, IP reset, and other
miscellaneous controls. It also contains some registers that
provide SoC information and status.
Signed-off-by: Jacky Huang <[email protected]>
---
include/linux/mfd/ma35d1-sys.h | 95 ++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
create mode 100644 include/linux/mfd/ma35d1-sys.h
diff --git a/include/linux/mfd/ma35d1-sys.h b/include/linux/mfd/ma35d1-sys.h
new file mode 100644
index 000000000000..dcd85231125d
--- /dev/null
+++ b/include/linux/mfd/ma35d1-sys.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Nuvoton Technologies.
+ * Author: Chi-Fen Li <[email protected]>
+ *
+ * System management control registers of MA35D1 SoC
+ */
+#ifndef __LINUX_MFD_MA35D1_SYS_H
+#define __LINUX_MFD_MA35D1_SYS_H
+
+#define REG_SYS_PDID (0x000) /* Product and Device Identifier */
+#define REG_SYS_PWRONOTP (0x004) /* Power-on Setting OTP Source */
+#define REG_SYS_PWRONPIN (0x008) /* Power-on Setting Pin Source */
+#define REG_SYS_RSTSTS (0x010) /* Reset Source Active Status */
+#define REG_SYS_MISCRFCR (0x014) /* Miscellaneous Reset Function */
+#define REG_SYS_RSTDEBCTL (0x018) /* Reset Pin De-bounce Control */
+#define REG_SYS_LVRDCR (0x01C) /* Low Voltage Reset & Detect */
+#define REG_SYS_IPRST0 (0x020) /* Reset Control Register 0 */
+#define REG_SYS_IPRST1 (0x024) /* Reset Control Register 1 */
+#define REG_SYS_IPRST2 (0x028) /* Reset Control Register 2 */
+#define REG_SYS_IPRST3 (0x02C) /* Reset Control Register 3 */
+#define REG_SYS_PMUCR (0x030) /* Power Management Unit Control */
+#define REG_SYS_DDRCQCSR (0x034) /* DDR Q Channel Control and Status */
+#define REG_SYS_PMUIEN (0x038) /* PMU Interrupt Enable */
+#define REG_SYS_PMUSTS (0x03C) /* PMU Status */
+#define REG_SYS_CA35WRBADR1 (0x040) /* A35 Core 1 Warm-boot Address */
+#define REG_SYS_CA35WRBPAR1 (0x044) /* A35 Core 1 Warm-boot Parameter */
+#define REG_SYS_CA35WRBADR2 (0x048) /* A35 Core 2 Warm-boot Address */
+#define REG_SYS_CA35WRBPAR2 (0x04C) /* A35 Core 2 Warm-boot Parameter */
+#define REG_SYS_USBPMISCR (0x060) /* USB PHY Miscellaneous Control */
+#define REG_SYS_USBP0PCR (0x064) /* USB Port 0 PHY Control */
+#define REG_SYS_USBP1PCR (0x068) /* USB Port 1 PHY Control */
+#define REG_SYS_MISCFCR0 (0x070) /* Miscellaneous Function Control 0 */
+#define REG_SYS_MISCFCR1 (0x074) /* Miscellaneous Function Control 1 */
+#define REG_SYS_MISCIER (0x078) /* Miscellaneous Interrupt Enable */
+#define REG_SYS_MISCISR (0x07C) /* Miscellaneous Interrupt Status */
+#define REG_SYS_GPA_MFPL (0x080) /* GPIOA Multi-Function Control LSB */
+#define REG_SYS_GPA_MFPH (0x084) /* GPIOA Multi-Function Control MSB */
+#define REG_SYS_GPB_MFPL (0x088) /* GPIOB Multi-Function Control LSB */
+#define REG_SYS_GPB_MFPH (0x08C) /* GPIOB Multi-Function Control MSB */
+#define REG_SYS_GPC_MFPL (0x090) /* GPIOC Multi-Function Control LSB */
+#define REG_SYS_GPC_MFPH (0x094) /* GPIOC Multi-Function Control MSB */
+#define REG_SYS_GPD_MFPL (0x098) /* GPIOD Multi-Function Control LSB */
+#define REG_SYS_GPD_MFPH (0x09C) /* GPIOD Multi-Function Control MSB */
+#define REG_SYS_GPE_MFPL (0x0A0) /* GPIOE Multi-Function Control LSB */
+#define REG_SYS_GPE_MFPH (0x0A4) /* GPIOE Multi-Function Control MSB */
+#define REG_SYS_GPF_MFPL (0x0A8) /* GPIOF Multi-Function Control LSB */
+#define REG_SYS_GPF_MFPH (0x0AC) /* GPIOF Multi-Function Control MSB */
+#define REG_SYS_GPG_MFPL (0x0B0) /* GPIOG Multi-Function Control LSB */
+#define REG_SYS_GPG_MFPH (0x0B4) /* GPIOG Multi-Function Control MSB */
+#define REG_SYS_GPH_MFPL (0x0B8) /* GPIOH Multi-Function Control LSB */
+#define REG_SYS_GPH_MFPH (0x0BC) /* GPIOH Multi-Function Control MSB */
+#define REG_SYS_GPI_MFPL (0x0C0) /* GPIOI Multi-Function Control LSB */
+#define REG_SYS_GPI_MFPH (0x0C4) /* GPIOI Multi-Function Control MSB */
+#define REG_SYS_GPJ_MFPL (0x0C8) /* GPIOJ Multi-Function Control LSB */
+#define REG_SYS_GPJ_MFPH (0x0CC) /* GPIOJ Multi-Function Control MSB */
+#define REG_SYS_GPK_MFPL (0x0D0) /* GPIOK Multi-Function Control LSB */
+#define REG_SYS_GPK_MFPH (0x0D4) /* GPIOK Multi-Function Control MSB */
+#define REG_SYS_GPL_MFPL (0x0D8) /* GPIOL Multi-Function Control LSB */
+#define REG_SYS_GPL_MFPH (0x0DC) /* GPIOL Multi-Function Control MSB */
+#define REG_SYS_GPM_MFPL (0x0E0) /* GPIOM Multi-Function Control LSB */
+#define REG_SYS_GPM_MFPH (0x0E4) /* GPIOM Multi-Function Control MSB */
+#define REG_SYS_GPN_MFPL (0x0E8) /* GPION Multi-Function Control LSB */
+#define REG_SYS_GPN_MFPH (0x0EC) /* GPION Multi-Function Control MSB */
+#define REG_SYS_HIRCFTRIM (0x100) /* HIRC Frequency Trim Value */
+#define REG_SYS_TSENSRFCR (0x104) /* Temperature Sensor Control */
+#define REG_SYS_GMAC0MISCR (0x108) /* GMAC 0 Miscellaneous Control */
+#define REG_SYS_GMAC1MISCR (0x10C) /* GMAC 1 Miscellaneous Control */
+#define REG_SYS_MACAD0LSR (0x110) /* MAC Address 0 LSW */
+#define REG_SYS_MACAD0HSR (0x114) /* MAC Address 0 HSW */
+#define REG_SYS_MACAD1LSR (0x118) /* MAC Address 1 LSW */
+#define REG_SYS_MACAD1HSR (0x11C) /* MAC Address 1 HSW */
+#define REG_SYS_CSDBGCTL (0x120) /* CoreSight Debug Control */
+#define REG_SYS_GPAB_MFOS (0x140) /* GPIOA/B Output Mode Select */
+#define REG_SYS_GPCD_MFOS (0x144) /* GPIOC/D Output Mode Select */
+#define REG_SYS_GPEF_MFOS (0x148) /* GPIOE/F Output Mode Select */
+#define REG_SYS_GPGH_MFOS (0x14C) /* GPIOG/H Output Mode Select */
+#define REG_SYS_GPIJ_MFOS (0x150) /* GPIOI/J Output Mode Select */
+#define REG_SYS_GPKL_MFOS (0x154) /* GPIOK/L Output Mode Select */
+#define REG_SYS_GPMN_MFOS (0x158) /* GPIOM/N Output Mode Select */
+#define REG_SYS_UID0 (0x180) /* Unique Identifier Word 0 */
+#define REG_SYS_UID1 (0x184) /* Unique Identifier Word 1 */
+#define REG_SYS_UID2 (0x188) /* Unique Identifier Word 2 */
+#define REG_SYS_UCID0 (0x190) /* Unique Customer Identifier 0 */
+#define REG_SYS_UCID1 (0x194) /* Unique Customer Identifier 1 */
+#define REG_SYS_UCID2 (0x198) /* Unique Customer Identifier 2 */
+#define REG_SYS_RLKTZS (0x1A0) /* TZS Register Lock Control */
+#define REG_SYS_RLKTZNS (0x1A4) /* TZNS Register Lock Control */
+#define REG_SYS_RLKSUBM (0x1A8) /* SubM Register Lock Control */
+#define REG_SYS_DPLPASWD (0x1B0) /* Deployed Password */
+
+void ma35d1_reg_lock(void);
+void ma35d1_reg_unlock(void);
+
+#endif /* __LINUX_MFD_MA35D1_SYS_H */
--
2.34.1
From: Jacky Huang <[email protected]>
Add the dt-bindings header for Nuvoton ma35d1, that gets shared
between the clock controller and clock references in the dts.
Signed-off-by: Jacky Huang <[email protected]>
---
.../dt-bindings/clock/nuvoton,ma35d1-clk.h | 253 ++++++++++++++++++
1 file changed, 253 insertions(+)
create mode 100644 include/dt-bindings/clock/nuvoton,ma35d1-clk.h
diff --git a/include/dt-bindings/clock/nuvoton,ma35d1-clk.h b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
new file mode 100644
index 000000000000..6c569fdd6e06
--- /dev/null
+++ b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Nuvoton Technologies.
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_NUVOTON_MA35D1_CLK_H
+#define __DT_BINDINGS_CLOCK_NUVOTON_MA35D1_CLK_H
+
+/* external and internal oscillator clocks */
+#define HXT 0
+#define HXT_GATE 1
+#define LXT 2
+#define LXT_GATE 3
+#define HIRC 4
+#define HIRC_GATE 5
+#define LIRC 6
+#define LIRC_GATE 7
+/* PLLs */
+#define CAPLL 8
+#define SYSPLL 9
+#define DDRPLL 10
+#define APLL 11
+#define EPLL 12
+#define VPLL 13
+/* EPLL divider */
+#define EPLL_DIV2 14
+#define EPLL_DIV4 15
+#define EPLL_DIV8 16
+/* CPU clock, system clock, AXI, HCLK and PCLK */
+#define CA35CLK_MUX 17
+#define AXICLK_DIV2 18
+#define AXICLK_DIV4 19
+#define AXICLK_MUX 20
+#define SYSCLK0_MUX 21
+#define SYSCLK1_MUX 22
+#define SYSCLK1_DIV2 23
+#define HCLK0 24
+#define HCLK1 25
+#define HCLK2 26
+#define PCLK0 27
+#define PCLK1 28
+#define PCLK2 29
+#define HCLK3 30
+#define PCLK3 31
+#define PCLK4 32
+/* AXI and AHB peripheral clocks */
+#define USBPHY0 33
+#define USBPHY1 34
+#define DDR0_GATE 35
+#define DDR6_GATE 36
+#define CAN0_MUX 37
+#define CAN0_DIV 38
+#define CAN0_GATE 39
+#define CAN1_MUX 40
+#define CAN1_DIV 41
+#define CAN1_GATE 42
+#define CAN2_MUX 43
+#define CAN2_DIV 44
+#define CAN2_GATE 45
+#define CAN3_MUX 46
+#define CAN3_DIV 47
+#define CAN3_GATE 48
+#define SDH0_MUX 49
+#define SDH0_GATE 50
+#define SDH1_MUX 51
+#define SDH1_GATE 52
+#define NAND_GATE 53
+#define USBD_GATE 54
+#define USBH_GATE 55
+#define HUSBH0_GATE 56
+#define HUSBH1_GATE 57
+#define GFX_MUX 58
+#define GFX_GATE 59
+#define VC8K_GATE 60
+#define DCU_MUX 61
+#define DCU_GATE 62
+#define DCUP_DIV 63
+#define EMAC0_GATE 64
+#define EMAC1_GATE 65
+#define CCAP0_MUX 66
+#define CCAP0_DIV 67
+#define CCAP0_GATE 68
+#define CCAP1_MUX 69
+#define CCAP1_DIV 70
+#define CCAP1_GATE 71
+#define PDMA0_GATE 72
+#define PDMA1_GATE 73
+#define PDMA2_GATE 74
+#define PDMA3_GATE 75
+#define WH0_GATE 76
+#define WH1_GATE 77
+#define HWS_GATE 78
+#define EBI_GATE 79
+#define SRAM0_GATE 80
+#define SRAM1_GATE 81
+#define ROM_GATE 82
+#define TRA_GATE 83
+#define DBG_MUX 84
+#define DBG_GATE 85
+#define CKO_MUX 86
+#define CKO_DIV 87
+#define CKO_GATE 88
+#define GTMR_GATE 89
+#define GPA_GATE 90
+#define GPB_GATE 91
+#define GPC_GATE 92
+#define GPD_GATE 93
+#define GPE_GATE 94
+#define GPF_GATE 95
+#define GPG_GATE 96
+#define GPH_GATE 97
+#define GPI_GATE 98
+#define GPJ_GATE 99
+#define GPK_GATE 100
+#define GPL_GATE 101
+#define GPM_GATE 102
+#define GPN_GATE 103
+/* APB peripheral clocks */
+#define TMR0_MUX 104
+#define TMR0_GATE 105
+#define TMR1_MUX 106
+#define TMR1_GATE 107
+#define TMR2_MUX 108
+#define TMR2_GATE 109
+#define TMR3_MUX 110
+#define TMR3_GATE 111
+#define TMR4_MUX 112
+#define TMR4_GATE 113
+#define TMR5_MUX 114
+#define TMR5_GATE 115
+#define TMR6_MUX 116
+#define TMR6_GATE 117
+#define TMR7_MUX 118
+#define TMR7_GATE 119
+#define TMR8_MUX 120
+#define TMR8_GATE 121
+#define TMR9_MUX 122
+#define TMR9_GATE 123
+#define TMR10_MUX 124
+#define TMR10_GATE 125
+#define TMR11_MUX 126
+#define TMR11_GATE 127
+#define UART0_MUX 128
+#define UART0_DIV 129
+#define UART0_GATE 130
+#define UART1_MUX 131
+#define UART1_DIV 132
+#define UART1_GATE 133
+#define UART2_MUX 134
+#define UART2_DIV 135
+#define UART2_GATE 136
+#define UART3_MUX 137
+#define UART3_DIV 138
+#define UART3_GATE 139
+#define UART4_MUX 140
+#define UART4_DIV 141
+#define UART4_GATE 142
+#define UART5_MUX 143
+#define UART5_DIV 144
+#define UART5_GATE 145
+#define UART6_MUX 146
+#define UART6_DIV 147
+#define UART6_GATE 148
+#define UART7_MUX 149
+#define UART7_DIV 150
+#define UART7_GATE 151
+#define UART8_MUX 152
+#define UART8_DIV 153
+#define UART8_GATE 154
+#define UART9_MUX 155
+#define UART9_DIV 156
+#define UART9_GATE 157
+#define UART10_MUX 158
+#define UART10_DIV 159
+#define UART10_GATE 160
+#define UART11_MUX 161
+#define UART11_DIV 162
+#define UART11_GATE 163
+#define UART12_MUX 164
+#define UART12_DIV 165
+#define UART12_GATE 166
+#define UART13_MUX 167
+#define UART13_DIV 168
+#define UART13_GATE 169
+#define UART14_MUX 170
+#define UART14_DIV 171
+#define UART14_GATE 172
+#define UART15_MUX 173
+#define UART15_DIV 174
+#define UART15_GATE 175
+#define UART16_MUX 176
+#define UART16_DIV 177
+#define UART16_GATE 178
+#define RTC_GATE 179
+#define DDR_GATE 180
+#define KPI_MUX 181
+#define KPI_DIV 182
+#define KPI_GATE 183
+#define I2C0_GATE 184
+#define I2C1_GATE 185
+#define I2C2_GATE 186
+#define I2C3_GATE 187
+#define I2C4_GATE 188
+#define I2C5_GATE 189
+#define QSPI0_MUX 190
+#define QSPI0_GATE 191
+#define QSPI1_MUX 192
+#define QSPI1_GATE 193
+#define SMC0_MUX 194
+#define SMC0_DIV 195
+#define SMC0_GATE 196
+#define SMC1_MUX 197
+#define SMC1_DIV 198
+#define SMC1_GATE 199
+#define WDT0_MUX 200
+#define WDT0_GATE 201
+#define WDT1_MUX 202
+#define WDT1_GATE 203
+#define WDT2_MUX 204
+#define WDT2_GATE 205
+#define WWDT0_MUX 206
+#define WWDT1_MUX 207
+#define WWDT2_MUX 208
+#define EPWM0_GATE 209
+#define EPWM1_GATE 210
+#define EPWM2_GATE 211
+#define I2S0_MUX 212
+#define I2S0_GATE 213
+#define I2S1_MUX 214
+#define I2S1_GATE 215
+#define SSMCC_GATE 216
+#define SSPCC_GATE 217
+#define SPI0_MUX 218
+#define SPI0_GATE 219
+#define SPI1_MUX 220
+#define SPI1_GATE 221
+#define SPI2_MUX 222
+#define SPI2_GATE 223
+#define SPI3_MUX 224
+#define SPI3_GATE 225
+#define ECAP0_GATE 226
+#define ECAP1_GATE 227
+#define ECAP2_GATE 228
+#define QEI0_GATE 229
+#define QEI1_GATE 230
+#define QEI2_GATE 231
+#define ADC_DIV 232
+#define ADC_GATE 233
+#define EADC_DIV 234
+#define EADC_GATE 235
+#define CLK_MAX_IDX 236
+
+#endif /* __DT_BINDINGS_CLOCK_NUVOTON_MA35D1_CLK_H */
--
2.34.1
From: Jacky Huang <[email protected]>
Add the dt-bindings header for Nuvoton ma35d1, that gets shared
between the reset controller and reset references in the dts.
Signed-off-by: Jacky Huang <[email protected]>
---
.../dt-bindings/reset/nuvoton,ma35d1-reset.h | 108 ++++++++++++++++++
1 file changed, 108 insertions(+)
create mode 100644 include/dt-bindings/reset/nuvoton,ma35d1-reset.h
diff --git a/include/dt-bindings/reset/nuvoton,ma35d1-reset.h b/include/dt-bindings/reset/nuvoton,ma35d1-reset.h
new file mode 100644
index 000000000000..6d0791b04d52
--- /dev/null
+++ b/include/dt-bindings/reset/nuvoton,ma35d1-reset.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 Nuvoton Technologies.
+ * Author: Chi-Fen Li <[email protected]>
+ *
+ * Device Tree binding constants for MA35D1 reset controller.
+ */
+
+#ifndef __DT_BINDINGS_RESET_MA35D1_H
+#define __DT_BINDINGS_RESET_MA35D1_H
+
+#define MA35D1_RESET_CHIP 0
+#define MA35D1_RESET_CA35CR0 1
+#define MA35D1_RESET_CA35CR1 2
+#define MA35D1_RESET_CM4 3
+#define MA35D1_RESET_PDMA0 4
+#define MA35D1_RESET_PDMA1 5
+#define MA35D1_RESET_PDMA2 6
+#define MA35D1_RESET_PDMA3 7
+#define MA35D1_RESET_DISP 9
+#define MA35D1_RESET_VCAP0 10
+#define MA35D1_RESET_VCAP1 11
+#define MA35D1_RESET_GFX 12
+#define MA35D1_RESET_VDEC 13
+#define MA35D1_RESET_WHC0 14
+#define MA35D1_RESET_WHC1 15
+#define MA35D1_RESET_GMAC0 16
+#define MA35D1_RESET_GMAC1 17
+#define MA35D1_RESET_HWSEM 18
+#define MA35D1_RESET_EBI 19
+#define MA35D1_RESET_HSUSBH0 20
+#define MA35D1_RESET_HSUSBH1 21
+#define MA35D1_RESET_HSUSBD 22
+#define MA35D1_RESET_USBHL 23
+#define MA35D1_RESET_SDH0 24
+#define MA35D1_RESET_SDH1 25
+#define MA35D1_RESET_NAND 26
+#define MA35D1_RESET_GPIO 27
+#define MA35D1_RESET_MCTLP 28
+#define MA35D1_RESET_MCTLC 29
+#define MA35D1_RESET_DDRPUB 30
+#define MA35D1_RESET_TMR0 34
+#define MA35D1_RESET_TMR1 35
+#define MA35D1_RESET_TMR2 36
+#define MA35D1_RESET_TMR3 37
+#define MA35D1_RESET_I2C0 40
+#define MA35D1_RESET_I2C1 41
+#define MA35D1_RESET_I2C2 42
+#define MA35D1_RESET_I2C3 43
+#define MA35D1_RESET_QSPI0 44
+#define MA35D1_RESET_SPI0 45
+#define MA35D1_RESET_SPI1 46
+#define MA35D1_RESET_SPI2 47
+#define MA35D1_RESET_UART0 48
+#define MA35D1_RESET_UART1 49
+#define MA35D1_RESET_UART2 50
+#define MA35D1_RESET_UAER3 51
+#define MA35D1_RESET_UART4 52
+#define MA35D1_RESET_UART5 53
+#define MA35D1_RESET_UART6 54
+#define MA35D1_RESET_UART7 55
+#define MA35D1_RESET_CANFD0 56
+#define MA35D1_RESET_CANFD1 57
+#define MA35D1_RESET_EADC0 60
+#define MA35D1_RESET_I2S0 61
+#define MA35D1_RESET_SC0 64
+#define MA35D1_RESET_SC1 65
+#define MA35D1_RESET_QSPI1 68
+#define MA35D1_RESET_SPI3 70
+#define MA35D1_RESET_EPWM0 80
+#define MA35D1_RESET_EPWM1 81
+#define MA35D1_RESET_QEI0 86
+#define MA35D1_RESET_QEI1 87
+#define MA35D1_RESET_ECAP0 90
+#define MA35D1_RESET_ECAP1 91
+#define MA35D1_RESET_CANFD2 92
+#define MA35D1_RESET_ADC0 95
+#define MA35D1_RESET_TMR4 96
+#define MA35D1_RESET_TMR5 97
+#define MA35D1_RESET_TMR6 98
+#define MA35D1_RESET_TMR7 99
+#define MA35D1_RESET_TMR8 100
+#define MA35D1_RESET_TMR9 101
+#define MA35D1_RESET_TMR10 102
+#define MA35D1_RESET_TMR11 103
+#define MA35D1_RESET_UART8 104
+#define MA35D1_RESET_UART9 105
+#define MA35D1_RESET_UART10 106
+#define MA35D1_RESET_UART11 107
+#define MA35D1_RESET_UART12 108
+#define MA35D1_RESET_UART13 109
+#define MA35D1_RESET_UART14 110
+#define MA35D1_RESET_UART15 111
+#define MA35D1_RESET_UART16 112
+#define MA35D1_RESET_I2S1 113
+#define MA35D1_RESET_I2C4 114
+#define MA35D1_RESET_I2C5 115
+#define MA35D1_RESET_EPWM2 116
+#define MA35D1_RESET_ECAP2 117
+#define MA35D1_RESET_QEI2 118
+#define MA35D1_RESET_CANFD3 119
+#define MA35D1_RESET_KPI 120
+#define MA35D1_RESET_GIC 124
+#define MA35D1_RESET_SSMCC 126
+#define MA35D1_RESET_SSPCC 127
+#define MA35D1_RESET_COUNT 128
+
+#endif
--
2.34.1
From: Jacky Huang <[email protected]>
Add Nuvoton ma35d1 system registers compatible
Signed-off-by: Jacky Huang <[email protected]>
---
Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
index c828c4f5e4a7..e7a3c6e1e77f 100644
--- a/Documentation/devicetree/bindings/mfd/syscon.yaml
+++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
@@ -57,6 +57,7 @@ properties:
- microchip,sparx5-cpu-syscon
- mstar,msc313-pmsleep
- nuvoton,wpcm450-shm
+ - nuvoton,ma35d1-sys
- rockchip,px30-qos
- rockchip,rk3036-qos
- rockchip,rk3066-qos
--
2.34.1
From: Jacky Huang <[email protected]>
Add binding for ARMv8 based Nuvotn SoCs and platform boards.
Add initial bindings for ma35d1 series development boards.
Signed-off-by: Jacky Huang <[email protected]>
---
.../devicetree/bindings/arm/nuvoton.yaml | 30 +++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/nuvoton.yaml
diff --git a/Documentation/devicetree/bindings/arm/nuvoton.yaml b/Documentation/devicetree/bindings/arm/nuvoton.yaml
new file mode 100644
index 000000000000..f95e7b30711e
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/nuvoton.yaml
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/nuvoton.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35 series SoC based platforms
+
+maintainers:
+ - Jacky Huang <[email protected]>
+
+description: |
+ Boards with an ARMv8 based Nuvoton MA35 series SoC shall have
+ the following properties.
+
+properties:
+ $nodename:
+ const: '/'
+ compatible:
+ oneOf:
+
+ - description: MA35D1 based boards
+ items:
+ - enum:
+ - nuvoton,ma35d1-iot
+ - nuvoton,ma35d1-som
+ - const: nuvoton,ma35d1
+
+additionalProperties: true
+...
--
2.34.1
From: Jacky Huang <[email protected]>
Add documentation to describe nuvoton ma35d1 clock driver bindings.
Signed-off-by: Jacky Huang <[email protected]>
---
.../bindings/clock/nuvoton,ma35d1-clk.yaml | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
diff --git a/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
new file mode 100644
index 000000000000..5c2dea071b38
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/nuvoton,ma35d1-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 Clock Controller Module Binding
+
+maintainers:
+ - Chi-Fang Li <[email protected]>
+ - Jacky Huang <[email protected]>
+
+description: |
+ The MA35D1 clock controller generates clocks for the whole chip,
+ including system clocks and all peripheral clocks.
+
+ See also:
+ include/dt-bindings/clock/ma35d1-clk.h
+
+properties:
+ compatible:
+ items:
+ - const: nuvoton,ma35d1-clk
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+ "#clock-cells":
+ const: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ const: clk_hxt
+
+ assigned-clocks:
+ maxItems: 5
+
+ assigned-clock-rates:
+ maxItems: 5
+
+ nuvoton,pll-mode:
+ description:
+ A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
+ EPLL, and VPLL in sequential. The operation mode value 0 is for
+ integer mode, 1 is for fractional mode, and 2 is for spread
+ spectrum mode.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ maxItems: 5
+ items:
+ minimum: 0
+ maximum: 2
+
+ nuvoton,sys:
+ description:
+ Phandle to the system management controller.
+ $ref: "/schemas/types.yaml#/definitions/phandle-array"
+
+required:
+ - compatible
+ - reg
+ - "#clock-cells"
+ - clocks
+ - clock-names
+ - nuvoton,sys
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+ clk: clock-controller@40460200 {
+ compatible = "nuvoton,ma35d1-clk", "syscon";
+ reg = <0x40460200 0x100>;
+ #clock-cells = <1>;
+ clocks = <&clk_hxt>;
+ clock-names = "clk_hxt";
+ nuvoton,sys = <&sys>;
+ };
+...
--
2.34.1
From: Jacky Huang <[email protected]>
Add documentation to describe nuvoton ma35d1 reset driver bindings.
Signed-off-by: Jacky Huang <[email protected]>
---
.../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
new file mode 100644
index 000000000000..f66c566c6dce
--- /dev/null
+++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 Reset Controller
+
+maintainers:
+ - Chi-Fang Li <[email protected]>
+ - Jacky Huang <[email protected]>
+
+description:
+ The system reset controller can be used to reset various peripheral
+ controllers in MA35D1 SoC.
+
+properties:
+ compatible:
+ const: nuvoton,ma35d1-reset
+
+ regmap:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: Phandle to the register map node.
+
+ '#reset-cells':
+ const: 1
+
+required:
+ - compatible
+ - regmap
+ - '#reset-cells'
+
+additionalProperties: false
+
+examples:
+ # system reset controller node:
+ - |
+ #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
+
+ sys: system-management@40460000 {
+ compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
+ reg = <0x40460000 0x200>;
+
+ reset: reset-controller {
+ compatible = "nuvoton,ma35d1-reset";
+ regmap = <&sys>;
+ #reset-cells = <1>;
+ };
+ };
+...
--
2.34.1
From: Jacky Huang <[email protected]>
Add documentation to describe nuvoton ma35d1 uart driver bindings.
Signed-off-by: Jacky Huang <[email protected]>
---
.../serial/nuvoton,ma35d1-serial.yaml | 52 +++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
diff --git a/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
new file mode 100644
index 000000000000..9daa2efd4734
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/nuvoton,ma35d1-serial.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 Universal Asynchronous Receiver/Transmitter (UART)
+
+maintainers:
+ - Min-Jen Chen <[email protected]>
+ - Jacky Huang <[email protected]>
+
+allOf:
+ - $ref: "serial.yaml"
+
+properties:
+ compatible:
+ const: nuvoton,ma35d1-uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+ aliases {
+ serial0 = &uart0;
+ };
+
+ uart0:serial@40700000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x40700000 0x100>;
+ interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART0_GATE>;
+ };
+...
--
2.34.1
From: Jacky Huang <[email protected]>
Add initial device tree support for Nuvoton ma35d1 SoC, including
cpu, clock, reset, and serial controllers.
Add reference boards som-256m and iot-512m.
Signed-off-by: Jacky Huang <[email protected]>
---
arch/arm64/boot/dts/nuvoton/Makefile | 2 +
.../boot/dts/nuvoton/ma35d1-iot-512m.dts | 24 ++
.../boot/dts/nuvoton/ma35d1-som-256m.dts | 23 ++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 272 ++++++++++++++++++
4 files changed, 321 insertions(+)
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
diff --git a/arch/arm64/boot/dts/nuvoton/Makefile b/arch/arm64/boot/dts/nuvoton/Makefile
index a99dab90472a..c11ab4eac9c7 100644
--- a/arch/arm64/boot/dts/nuvoton/Makefile
+++ b/arch/arm64/boot/dts/nuvoton/Makefile
@@ -1,2 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_NPCM) += nuvoton-npcm845-evb.dtb
+dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-iot-512m.dtb
+dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-som-256m.dtb
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
new file mode 100644
index 000000000000..dffcaef1e6d8
--- /dev/null
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Shan-Chun Hung <[email protected]>
+ * Jacky huang <[email protected]>
+ */
+
+/dts-v1/;
+#include "ma35d1.dtsi"
+
+/ {
+ model = "Nuvoton MA35D1-IoT";
+ compatible = "nuvoton,ma35d1-iot", "nuvoton,ma35d1";
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ mem: memory@80000000 {
+ device_type = "memory";
+ reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
+ };
+};
+
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
new file mode 100644
index 000000000000..3e6c3d5469ac
--- /dev/null
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Shan-Chun Hung <[email protected]>
+ * Jacky huang <[email protected]>
+ */
+
+/dts-v1/;
+#include "ma35d1.dtsi"
+
+/ {
+ model = "Nuvoton MA35D1-SOM";
+ compatible = "nuvoton,ma35d1-som", "nuvoton,ma35d1";
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ mem: memory@80000000 {
+ device_type = "memory";
+ reg = <0x00000000 0x80000000 0 0x10000000>; /* 256M DRAM */
+ };
+};
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
new file mode 100644
index 000000000000..8c855f6b330a
--- /dev/null
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Shan-Chun Hung <[email protected]>
+ * Jacky huang <[email protected]>
+ */
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
+
+/ {
+ compatible = "nuvoton,ma35d1";
+ interrupt-parent = <&gic>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ aliases {
+ serial0 = &uart0;
+ serial1 = &uart1;
+ serial2 = &uart2;
+ serial3 = &uart3;
+ serial4 = &uart4;
+ serial5 = &uart5;
+ serial6 = &uart6;
+ serial7 = &uart7;
+ serial8 = &uart8;
+ serial9 = &uart9;
+ serial10 = &uart10;
+ serial11 = &uart11;
+ serial12 = &uart12;
+ serial13 = &uart13;
+ serial14 = &uart14;
+ serial15 = &uart15;
+ serial16 = &uart16;
+ };
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ cpus {
+ #address-cells = <2>;
+ #size-cells = <0>;
+ cpu0: cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a35";
+ reg = <0x0 0x0>;
+ enable-method = "psci";
+ next-level-cache = <&L2_0>;
+ };
+ cpu1: cpu@1 {
+ device_type = "cpu";
+ compatible = "arm,cortex-a35";
+ reg = <0x0 0x1>;
+ enable-method = "psci";
+ next-level-cache = <&L2_0>;
+ };
+ L2_0: l2-cache0 {
+ compatible = "cache";
+ cache-level = <2>;
+ };
+ };
+
+ psci {
+ compatible = "arm,psci-0.2";
+ method = "smc";
+ };
+
+ clk_hxt: clock_hxt {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <24000000>;
+ clock-output-names = "clk_hxt";
+ };
+
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) |
+ IRQ_TYPE_LEVEL_LOW)>, /* Physical Secure */
+ <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) |
+ IRQ_TYPE_LEVEL_LOW)>, /* Physical Non-Secure */
+ <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) |
+ IRQ_TYPE_LEVEL_LOW)>, /* Virtual */
+ <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) |
+ IRQ_TYPE_LEVEL_LOW)>; /* Hypervisor */
+ clock-frequency = <12000000>;
+ interrupt-parent = <&gic>;
+ };
+
+ sys: system-management@40460000 {
+ compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
+ reg = <0x0 0x40460000 0x0 0x200>;
+
+ reset: reset-controller {
+ compatible = "nuvoton,ma35d1-reset";
+ regmap = <&sys>;
+ #reset-cells = <1>;
+ };
+ };
+
+ clk: clock-controller@40460200 {
+ compatible = "nuvoton,ma35d1-clk", "syscon";
+ reg = <0x00000000 0x40460200 0x0 0x100>;
+ #clock-cells = <1>;
+ clocks = <&clk_hxt>;
+ clock-names = "clk_hxt";
+ assigned-clocks = <&clk CAPLL>,
+ <&clk DDRPLL>,
+ <&clk APLL>,
+ <&clk EPLL>,
+ <&clk VPLL>;
+ assigned-clock-rates = <800000000>,
+ <266000000>,
+ <180000000>,
+ <500000000>,
+ <102000000>;
+ nuvoton,pll-mode = <0>, <1>, <0>, <0>, <0>;
+ nuvoton,sys = <&sys>;
+ };
+
+ gic: interrupt-controller@50801000 {
+ compatible = "arm,gic-400";
+ #interrupt-cells = <3>;
+ interrupt-parent = <&gic>;
+ interrupt-controller;
+ reg = <0x0 0x50801000 0 0x1000>, /* GICD */
+ <0x0 0x50802000 0 0x2000>, /* GICC */
+ <0x0 0x50804000 0 0x2000>, /* GICH */
+ <0x0 0x50806000 0 0x2000>; /* GICV */
+ interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0x13) |
+ IRQ_TYPE_LEVEL_HIGH)>;
+ };
+
+ uart0:serial@40700000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40700000 0x0 0x100>;
+ interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART0_GATE>;
+ status = "okay";
+ };
+
+ uart1:serial@40710000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40710000 0x0 0x100>;
+ interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART1_GATE>;
+ status = "disabled";
+ };
+
+ uart2:serial@40720000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40720000 0x0 0x100>;
+ interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART2_GATE>;
+ status = "disabled";
+ };
+
+ uart3:serial@40730000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40730000 0x0 0x100>;
+ interrupts = <GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART3_GATE>;
+ status = "disabled";
+ };
+
+ uart4:serial@40740000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40740000 0x0 0x100>;
+ interrupts = <GIC_SPI 63 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART4_GATE>;
+ status = "disabled";
+ };
+
+ uart5:serial@40750000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40750000 0x0 0x100>;
+ interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART5_GATE>;
+ status = "disabled";
+ };
+
+ uart6:serial@40760000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40760000 0x0 0x100>;
+ interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART6_GATE>;
+ status = "disabled";
+ };
+
+ uart7:serial@40770000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40770000 0x0 0x100>;
+ interrupts = <GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART7_GATE>;
+ status = "disabled";
+ };
+
+ uart8:serial@40780000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40780000 0x0 0x100>;
+ interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART8_GATE>;
+ status = "disabled";
+ };
+
+ uart9:serial@40790000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40790000 0x0 0x100>;
+ interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART9_GATE>;
+ status = "disabled";
+ };
+
+ uart10:serial@407a0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407a0000 0x0 0x100>;
+ interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART10_GATE>;
+ status = "disabled";
+ };
+
+ uart11:serial@407b0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407b0000 0x0 0x100>;
+ interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART11_GATE>;
+ status = "disabled";
+ };
+
+ uart12:serial@407c0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407c0000 0x0 0x100>;
+ interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART12_GATE>;
+ status = "disabled";
+ };
+
+ uart13:serial@407d0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407d0000 0x0 0x100>;
+ interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART13_GATE>;
+ status = "disabled";
+ };
+
+ uart14:serial@407e0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407e0000 0x0 0x100>;
+ interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART14_GATE>;
+ status = "disabled";
+ };
+
+ uart15:serial@407f0000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x407f0000 0x0 0x100>;
+ interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART15_GATE>;
+ status = "disabled";
+ };
+
+ uart16:serial@40880000 {
+ compatible = "nuvoton,ma35d1-uart";
+ reg = <0x0 0x40880000 0x0 0x100>;
+ interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk UART16_GATE>;
+ status = "disabled";
+ };
+};
--
2.34.1
From: Jacky Huang <[email protected]>
This driver supports individual IP reset for ma35d1. The reset
control registers is a subset of system control registers.
Signed-off-by: Jacky Huang <[email protected]>
---
drivers/reset/Kconfig | 6 ++
drivers/reset/Makefile | 1 +
drivers/reset/reset-ma35d1.c | 152 +++++++++++++++++++++++++++++++++++
3 files changed, 159 insertions(+)
create mode 100644 drivers/reset/reset-ma35d1.c
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 2a52c990d4fe..47671060d259 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -143,6 +143,12 @@ config RESET_NPCM
This enables the reset controller driver for Nuvoton NPCM
BMC SoCs.
+config RESET_NUVOTON_MA35D1
+ bool "Nuvton MA35D1 Reset Driver"
+ default ARCH_NUVOTON
+ help
+ This enables the reset controller driver for Nuvoton MA35D1 SoC.
+
config RESET_OXNAS
bool
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 3e7e5fd633a8..fd52dcf66a99 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_RESET_MCHP_SPARX5) += reset-microchip-sparx5.o
obj-$(CONFIG_RESET_MESON) += reset-meson.o
obj-$(CONFIG_RESET_MESON_AUDIO_ARB) += reset-meson-audio-arb.o
obj-$(CONFIG_RESET_NPCM) += reset-npcm.o
+obj-$(CONFIG_RESET_NUVOTON_MA35D1) += reset-ma35d1.o
obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
obj-$(CONFIG_RESET_POLARFIRE_SOC) += reset-mpfs.o
diff --git a/drivers/reset/reset-ma35d1.c b/drivers/reset/reset-ma35d1.c
new file mode 100644
index 000000000000..bdd39483ca4e
--- /dev/null
+++ b/drivers/reset/reset-ma35d1.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Chi-Fang Li <[email protected]>
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/mfd/syscon.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/mfd/ma35d1-sys.h>
+#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
+#include <linux/regmap.h>
+#include <linux/reboot.h>
+
+#define RST_PRE_REG 32
+
+struct ma35d1_reset_data {
+ struct reset_controller_dev rcdev;
+ struct regmap *regmap;
+};
+
+struct ma35d1_reboot_data {
+ struct notifier_block restart_handler;
+ struct regmap *regmap;
+};
+
+static int ma35d1_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct ma35d1_reboot_data *data =
+ container_of(this, struct ma35d1_reboot_data,
+ restart_handler);
+ regmap_write(data->regmap, REG_SYS_IPRST0, 1 << MA35D1_RESET_CHIP);
+ return -EAGAIN;
+}
+
+static int ma35d1_reset_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ int reg;
+ int offset = (id / RST_PRE_REG) * 4;
+ struct ma35d1_reset_data *data =
+ container_of(rcdev, struct ma35d1_reset_data, rcdev);
+
+ regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
+ if (assert)
+ reg |= 1 << (id % RST_PRE_REG);
+ else
+ reg &= ~(1 << (id % RST_PRE_REG));
+
+ regmap_write(data->regmap, REG_SYS_IPRST0 + offset, reg);
+ return 0;
+}
+
+static int ma35d1_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return ma35d1_reset_update(rcdev, id, true);
+}
+
+static int ma35d1_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return ma35d1_reset_update(rcdev, id, false);
+}
+
+static int ma35d1_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ int reg;
+ int offset = id / RST_PRE_REG;
+ struct ma35d1_reset_data *data =
+ container_of(rcdev, struct ma35d1_reset_data, rcdev);
+
+ regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
+ return !!(reg & BIT(id % RST_PRE_REG));
+}
+
+static const struct reset_control_ops ma35d1_reset_ops = {
+ .assert = ma35d1_reset_assert,
+ .deassert = ma35d1_reset_deassert,
+ .status = ma35d1_reset_status,
+};
+
+static const struct of_device_id ma35d1_reset_dt_ids[] = {
+ { .compatible = "nuvoton,ma35d1-reset" },
+ { },
+};
+
+static int ma35d1_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ma35d1_reset_data *reset_data;
+ struct ma35d1_reboot_data *reboot_data;
+ int err;
+
+ if (!pdev->dev.of_node) {
+ dev_err(&pdev->dev, "Device tree node not found\n");
+ return -EINVAL;
+ }
+
+ reset_data = devm_kzalloc(dev, sizeof(*reset_data), GFP_KERNEL);
+ if (!reset_data)
+ return -ENOMEM;
+
+ reboot_data = devm_kzalloc(dev, sizeof(*reboot_data), GFP_KERNEL);
+ if (!reboot_data) {
+ devm_kfree(dev, reset_data);
+ return -ENOMEM;
+ }
+
+ reset_data->regmap = syscon_regmap_lookup_by_phandle(
+ pdev->dev.of_node, "regmap");
+ if (IS_ERR(reset_data->regmap)) {
+ dev_err(&pdev->dev, "Failed to get SYS register base\n");
+ err = PTR_ERR(reset_data->regmap);
+ goto err_out;
+ }
+ reset_data->rcdev.owner = THIS_MODULE;
+ reset_data->rcdev.nr_resets = MA35D1_RESET_COUNT;
+ reset_data->rcdev.ops = &ma35d1_reset_ops;
+ reset_data->rcdev.of_node = dev->of_node;
+
+ reboot_data->regmap = reset_data->regmap;
+ reboot_data->restart_handler.notifier_call = ma35d1_restart_handler;
+ reboot_data->restart_handler.priority = 192;
+
+ err = register_restart_handler(&reboot_data->restart_handler);
+ if (err)
+ dev_warn(&pdev->dev, "failed to register restart handler\n");
+
+ return devm_reset_controller_register(dev, &reset_data->rcdev);
+
+err_out:
+ devm_kfree(dev, reset_data);
+ devm_kfree(dev, reboot_data);
+ return err;
+}
+
+static struct platform_driver ma35d1_reset_driver = {
+ .probe = ma35d1_reset_probe,
+ .driver = {
+ .name = "ma35d1-reset",
+ .of_match_table = ma35d1_reset_dt_ids,
+ },
+};
+
+builtin_platform_driver(ma35d1_reset_driver);
--
2.34.1
From: Jacky Huang <[email protected]>
The clock controller generates clocks for the whole chip, including
system clocks and all peripheral clocks. This driver support ma35d1
clock gating, divider, and individual PLL configuration.
There are 6 PLLs in ma35d1 SoC:
- CA-PLL for the two Cortex-A35 CPU clock
- SYS-PLL for system bus, which comes from the companion MCU
and cannot be programmed by clock controller.
- DDR-PLL for DDR
- EPLL for GMAC and GFX, Display, and VDEC IPs.
- VPLL for video output pixel clock
- APLL for SDHC, I2S audio, and other IPs.
CA-PLL has only one operation mode.
DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
operation modes: integer mode, fraction mode, and spread specturm mode.
Signed-off-by: Jacky Huang <[email protected]>
---
drivers/clk/Makefile | 1 +
drivers/clk/nuvoton/Makefile | 4 +
drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 ++++
drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 +++++++++++++
drivers/clk/nuvoton/clk-ma35d1.c | 970 +++++++++++++++++++++++
drivers/clk/nuvoton/clk-ma35d1.h | 198 +++++
6 files changed, 1851 insertions(+)
create mode 100644 drivers/clk/nuvoton/Makefile
create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index e3ca0d058a25..2e7916d269e1 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -103,6 +103,7 @@ endif
obj-y += mstar/
obj-y += mvebu/
obj-$(CONFIG_ARCH_MXS) += mxs/
+obj-$(CONFIG_ARCH_NUVOTON) += nuvoton/
obj-$(CONFIG_COMMON_CLK_NXP) += nxp/
obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pistachio/
obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
diff --git a/drivers/clk/nuvoton/Makefile b/drivers/clk/nuvoton/Makefile
new file mode 100644
index 000000000000..d2c092541b8d
--- /dev/null
+++ b/drivers/clk/nuvoton/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1.o
+obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-divider.o
+obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-pll.o
diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c
new file mode 100644
index 000000000000..5f4791531e47
--- /dev/null
+++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Chi-Fang Li <[email protected]>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+
+#include "clk-ma35d1.h"
+
+#define div_mask(width) ((1 << (width)) - 1)
+
+struct ma35d1_adc_clk_divider {
+ struct clk_hw hw;
+ void __iomem *reg;
+ u8 shift;
+ u8 width;
+ u32 mask;
+ const struct clk_div_table *table;
+ spinlock_t *lock;
+};
+
+#define to_ma35d1_adc_clk_divider(_hw) \
+ container_of(_hw, struct ma35d1_adc_clk_divider, hw)
+
+static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ unsigned int val;
+ struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
+
+ val = readl_relaxed(dclk->reg) >> dclk->shift;
+ val &= div_mask(dclk->width);
+ val += 1;
+ return divider_recalc_rate(hw, parent_rate, val, dclk->table,
+ CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
+}
+
+static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
+
+ return divider_round_rate(hw, rate, prate, dclk->table,
+ dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
+}
+
+static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ int value;
+ unsigned long flags = 0;
+ u32 data;
+ struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
+
+ value = divider_get_val(rate, parent_rate, dclk->table,
+ dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
+
+ if (dclk->lock)
+ spin_lock_irqsave(dclk->lock, flags);
+
+ data = readl_relaxed(dclk->reg);
+ data &= ~(div_mask(dclk->width) << dclk->shift);
+ data |= (value - 1) << dclk->shift;
+ data |= dclk->mask;
+
+ writel_relaxed(data, dclk->reg);
+
+ if (dclk->lock)
+ spin_unlock_irqrestore(dclk->lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops ma35d1_adc_clkdiv_ops = {
+ .recalc_rate = ma35d1_clkdiv_recalc_rate,
+ .round_rate = ma35d1_clkdiv_round_rate,
+ .set_rate = ma35d1_clkdiv_set_rate,
+};
+
+struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
+ const char *parent_name,
+ unsigned long flags, void __iomem *reg,
+ u8 shift, u8 width, u32 mask_bit)
+{
+ struct ma35d1_adc_clk_divider *div;
+ struct clk_init_data init;
+ struct clk_div_table *table;
+ u32 max_div, min_div;
+ struct clk_hw *hw;
+ int ret;
+ int i;
+
+ /* allocate the divider */
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ /* Init the divider table */
+ max_div = div_mask(width) + 1;
+ min_div = 1;
+
+ table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
+ if (!table) {
+ kfree(div);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ for (i = 0; i < max_div; i++) {
+ table[i].val = (min_div + i);
+ table[i].div = 2 * table[i].val;
+ }
+ table[max_div].val = 0;
+ table[max_div].div = 0;
+
+ init.name = name;
+ init.ops = &ma35d1_adc_clkdiv_ops;
+ init.flags |= flags;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+
+ /* struct ma35d1_adc_clk_divider assignments */
+ div->reg = reg;
+ div->shift = shift;
+ div->width = width;
+ div->mask = mask_bit ? BIT(mask_bit) : 0;
+ div->lock = &ma35d1_lock;
+ div->hw.init = &init;
+ div->table = table;
+
+ /* Register the clock */
+ hw = &div->hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(table);
+ kfree(div);
+ return ERR_PTR(ret);
+ }
+ return hw;
+}
diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
new file mode 100644
index 000000000000..79e724b148fa
--- /dev/null
+++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Chi-Fang Li <[email protected]>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/bitfield.h>
+
+#include "clk-ma35d1.h"
+
+#define to_ma35d1_clk_pll(clk) \
+ (container_of(clk, struct ma35d1_clk_pll, clk))
+
+#define PLL0CTL0_FBDIV_MSK GENMASK(7, 0)
+#define PLL0CTL0_INDIV_MSK GENMASK(11, 8)
+#define PLL0CTL0_OUTDIV_MSK GENMASK(13, 12)
+#define PLL0CTL0_PD_MSK BIT(16)
+#define PLL0CTL0_BP_MSK BIT(17)
+#define PLLXCTL0_FBDIV_MSK GENMASK(10, 0)
+#define PLLXCTL0_INDIV_MSK GENMASK(17, 12)
+#define PLLXCTL0_MODE_MSK GENMASK(19, 18)
+#define PLLXCTL0_SSRATE_MSK GENMASK(30, 20)
+#define PLLXCTL1_PD_MSK BIT(0)
+#define PLLXCTL1_BP_MSK BIT(1)
+#define PLLXCTL1_OUTDIV_MSK GENMASK(6, 4)
+#define PLLXCTL1_FRAC_MSK GENMASK(31, 8)
+#define PLLXCTL2_SLOPE_MSK GENMASK(23, 0)
+
+struct ma35d1_clk_pll {
+ struct clk_hw hw;
+ u8 type;
+ u8 mode;
+ unsigned long rate;
+ void __iomem *ctl0_base;
+ void __iomem *ctl1_base;
+ void __iomem *ctl2_base;
+ struct regmap *regmap;
+};
+
+struct vsipll_freq_conf_reg_tbl {
+ unsigned long freq;
+ u8 mode;
+ u32 ctl0_reg;
+ u32 ctl1_reg;
+ u32 ctl2_reg;
+};
+
+static const struct vsipll_freq_conf_reg_tbl ma35d1pll_freq[] = {
+ { 1000000000, VSIPLL_INTEGER_MODE, 0x307d, 0x10, 0 },
+ { 884736000, VSIPLL_FRACTIONAL_MODE, 0x41024, 0xdd2f1b11, 0 },
+ { 533000000, VSIPLL_SS_MODE, 0x12b8102c, 0x6aaaab20, 0x12317 },
+ { }
+};
+
+static void CLK_UnLockReg(struct ma35d1_clk_pll *pll)
+{
+ int ret;
+
+ /* Unlock PLL registers */
+ do {
+ regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x59);
+ regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x16);
+ regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x88);
+ regmap_read(pll->regmap, REG_SYS_RLKTZNS, &ret);
+ } while (ret == 0);
+}
+
+static void CLK_LockReg(struct ma35d1_clk_pll *pll)
+{
+ /* Lock PLL registers */
+ regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x0);
+}
+
+/* SMIC PLL for CAPLL */
+unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
+ unsigned long PllSrcClk)
+{
+ u32 u32M, u32N, u32P, u32OutDiv;
+ u32 val;
+ unsigned long u64PllClk;
+ u32 clk_div_table[] = { 1, 2, 4, 8};
+
+ val = __raw_readl(pll->ctl0_base);
+
+ u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
+ u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
+ u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
+ u32OutDiv = clk_div_table[u32P];
+
+ if (val & PLL0CTL0_BP_MSK) {
+ u64PllClk = PllSrcClk;
+ } else {
+ u64PllClk = PllSrcClk * u32N;
+ do_div(u64PllClk, u32M * u32OutDiv);
+ }
+ return u64PllClk;
+}
+
+/* VSI-PLL: INTEGER_MODE */
+unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
+ unsigned long u64PllFreq, u32 *u32Reg)
+{
+ u32 u32TmpM, u32TmpN, u32TmpP;
+ u32 u32RngMinN, u32RngMinM, u32RngMinP;
+ u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
+ u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
+ unsigned long u64PllClk;
+ unsigned long u64Con1, u64Con2, u64Con3;
+
+ u64PllClk = 0;
+ u32Min = (u32) -1;
+
+ if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
+ (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
+ u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
+ u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
+ u64PllClk = ma35d1pll_freq[0].freq;
+ return u64PllClk;
+ }
+
+ u32RngMinM = 1UL;
+ u32RngMaxM = 63UL;
+ u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
+ (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
+ u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
+ (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
+
+ for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
+ u64Con1 = PllSrcClk / u32TmpM;
+ u32RngMinN = 16UL;
+ u32RngMaxN = 2047UL;
+ u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
+ (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
+ u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
+ (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
+
+ for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
+ u32TmpN++) {
+ u64Con2 = u64Con1 * u32TmpN;
+ u32RngMinP = 1UL;
+ u32RngMaxP = 7UL;
+ u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
+ (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
+ u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
+ u32RngMaxP) ?
+ (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
+ u32RngMaxP;
+ for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
+ u32TmpP++) {
+ u64Con3 = u64Con2 / u32TmpP;
+ if (u64Con3 > u64PllFreq)
+ u32Tmp = u64Con3 - u64PllFreq;
+ else
+ u32Tmp = u64PllFreq - u64Con3;
+
+ if (u32Tmp < u32Min) {
+ u32Min = u32Tmp;
+ u32MinM = u32TmpM;
+ u32MinN = u32TmpN;
+ u32MinP = u32TmpP;
+
+ if (u32Min == 0UL) {
+ u32Reg[0] = (u32MinM << 12) |
+ (u32MinN);
+ u32Reg[1] = (u32MinP << 4);
+ return ((PllSrcClk * u32MinN) /
+ (u32MinP * u32MinM));
+ }
+ }
+ }
+ }
+ }
+
+ u32Reg[0] = (u32MinM << 12) | (u32MinN);
+ u32Reg[1] = (u32MinP << 4);
+ u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
+ return u64PllClk;
+}
+
+/* VSI-PLL: FRACTIONAL_MODE */
+unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
+ unsigned long u64PllFreq, u32 *u32Reg)
+{
+ unsigned long u64X, u64N, u64M, u64P, u64tmp;
+ unsigned long u64PllClk, u64FCLKO;
+ u32 u32FRAC;
+
+ if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
+ u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
+ u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
+ u64PllClk = ma35d1pll_freq[1].freq;
+ return u64PllClk;
+ }
+
+ if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
+ u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
+ ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
+ } else {
+ pr_err("Failed to set rate %ld\n", u64PllFreq);
+ return 0;
+ }
+
+ u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
+ ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
+ ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
+
+ if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
+ (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
+ return 0;
+
+ u64M = (PllSrcClk <= VSIPLL_FREFDIVM_MAX_FREQ) ? 1 :
+ ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) +
+ ((PllSrcClk % VSIPLL_FREFDIVM_MAX_FREQ) ? 1 : 0));
+
+ u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
+ u64N = u64tmp / 1000;
+ u64X = u64tmp % 1000;
+ u32FRAC = ((u64X << 24) + 500) / 1000;
+ u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
+
+ u32Reg[0] = (u64M << 12) | (u64N);
+ u32Reg[1] = (u64P << 4) | (u32FRAC << 8);
+ return u64PllClk;
+}
+
+/* VSI-PLL: SS_MODE */
+unsigned long CLK_CalPLLFreq_Mode2(unsigned long PllSrcClk,
+ unsigned long u64PllFreq,
+ u32 u32SR, u32 u32Fmod, u32 *u32Reg)
+{
+ unsigned long u64X, u64N, u64M, u64P, u64tmp, u64tmpP, u64tmpM;
+ unsigned long u64SSRATE, u64SLOPE, u64PllClk, u64FCLKO;
+ u32 u32FRAC, i;
+
+ if (u64PllFreq >= VSIPLL_FCLKO_MAX_FREQ) {
+ u32Reg[0] = ma35d1pll_freq[2].ctl0_reg;
+ u32Reg[1] = ma35d1pll_freq[2].ctl1_reg;
+ u32Reg[2] = ma35d1pll_freq[2].ctl2_reg;
+ u64PllClk = ma35d1pll_freq[2].freq;
+ return u64PllClk;
+ }
+
+ if (u64PllFreq < VSIPLL_FCLKO_MIN_FREQ) {
+ u64FCLKO = 0;
+ for (i = 2; i < 8; i++) {
+ u64tmp = (i * u64PllFreq);
+ if (u64tmp > VSIPLL_FCLKO_MIN_FREQ)
+ u64FCLKO = u64tmp;
+ }
+ if (u64FCLKO == 0) {
+ pr_err("Failed to set rate %ld\n", u64PllFreq);
+ return 0;
+ }
+
+ } else
+ u64FCLKO = u64PllFreq;
+
+ u64P = 0;
+ for (i = 1; i < 8; i++) {
+ u64tmpP = i * u64FCLKO;
+ if ((u64tmpP <= VSIPLL_FCLK_MAX_FREQ) &&
+ (u64tmpP >= VSIPLL_FCLK_MIN_FREQ)) {
+ u64P = i;
+ break;
+ }
+ }
+
+ if (u64P == 0)
+ return 0;
+
+ u64M = 0;
+ for (i = 1; i < 64; i++) {
+ u64tmpM = PllSrcClk / i;
+ if ((u64tmpM <= VSIPLL_FREFDIVM_MAX_FREQ) &&
+ (u64tmpM >= VSIPLL_FREFDIVM_MIN_FREQ1)) {
+ u64M = i;
+ break;
+ }
+ }
+
+ if (u64M == 0)
+ return 0;
+
+ u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
+ u64N = u64tmp / 1000;
+ u64X = u64tmp % 1000;
+ u32FRAC = ((u64X << 24) + 500) / 1000;
+
+ u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
+ u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
+
+ u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
+
+ u32Reg[0] = (u64SSRATE << VSIPLLCTL0_SSRATE_POS) | (u64M <<
+ VSIPLLCTL0_INDIV_POS) | (u64N);
+ u32Reg[1] = (u64P << VSIPLLCTL1_OUTDIV_POS) | (u32FRAC << VSIPLLCTL1_FRAC_POS);
+ u32Reg[2] = u64SLOPE;
+ return u64PllClk;
+}
+
+unsigned long CLK_SetPLLFreq(struct ma35d1_clk_pll *pll,
+ unsigned long PllSrcClk,
+ unsigned long u64PllFreq)
+{
+ u32 u32Reg[3] = { 0 }, val_ctl0, val_ctl1, val_ctl2;
+ unsigned long u64PllClk;
+
+ val_ctl0 = __raw_readl(pll->ctl0_base);
+ val_ctl1 = __raw_readl(pll->ctl1_base);
+ val_ctl2 = __raw_readl(pll->ctl2_base);
+
+ switch (pll->mode) {
+ case VSIPLL_INTEGER_MODE:
+ u64PllClk = CLK_CalPLLFreq_Mode0(PllSrcClk, u64PllFreq,
+ u32Reg);
+ val_ctl0 = u32Reg[0] |
+ (VSIPLL_INTEGER_MODE << VSIPLLCTL0_MODE_POS);
+ break;
+ case VSIPLL_FRACTIONAL_MODE:
+ u64PllClk = CLK_CalPLLFreq_Mode1(PllSrcClk, u64PllFreq,
+ u32Reg);
+ val_ctl0 = u32Reg[0] |
+ (VSIPLL_FRACTIONAL_MODE << VSIPLLCTL0_MODE_POS);
+ break;
+ case VSIPLL_SS_MODE:
+ u64PllClk = CLK_CalPLLFreq_Mode2(PllSrcClk, u64PllFreq,
+ VSIPLL_MODULATION_FREQ,
+ VSIPLL_SPREAD_RANGE, u32Reg);
+ val_ctl0 = u32Reg[0] |
+ (VSIPLL_SS_MODE << VSIPLLCTL0_MODE_POS);
+ break;
+ }
+
+ val_ctl1 = VSIPLLCTL1_PD_MSK | u32Reg[1];
+ val_ctl2 = u32Reg[2];
+
+ __raw_writel(val_ctl0, pll->ctl0_base);
+ __raw_writel(val_ctl1, pll->ctl1_base);
+ __raw_writel(val_ctl2, pll->ctl2_base);
+ return u64PllClk;
+}
+
+unsigned long CLK_GetPLLFreq_VSIPLL(struct ma35d1_clk_pll *pll,
+ unsigned long PllSrcClk)
+{
+ u32 u32M, u32N, u32P, u32X, u32SR, u32FMOD;
+ u32 val_ctl0, val_ctl1, val_ctl2;
+ unsigned long u64PllClk, u64X;
+
+ val_ctl0 = __raw_readl(pll->ctl0_base);
+ val_ctl1 = __raw_readl(pll->ctl1_base);
+ val_ctl2 = __raw_readl(pll->ctl2_base);
+
+ if (val_ctl1 & PLLXCTL1_BP_MSK) {
+ u64PllClk = PllSrcClk;
+ return u64PllClk;
+ }
+
+ if (pll->mode == VSIPLL_INTEGER_MODE) {
+ u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
+ u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
+ u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
+
+ u64PllClk = PllSrcClk * u32N;
+ do_div(u64PllClk, u32M * u32P);
+
+ } else if (pll->mode == VSIPLL_FRACTIONAL_MODE) {
+ u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
+ u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
+ u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
+ u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
+ u64X = (u64) u32X;
+ u64X = (((u64X * 1000) + 500) >> 24);
+ u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
+ 1000 / u32P / u32M;
+
+ } else {
+ u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
+ u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
+ u32SR = FIELD_GET(PLLXCTL0_SSRATE_MSK, val_ctl0);
+ u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
+ u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
+ u32FMOD = FIELD_GET(PLLXCTL2_SLOPE_MSK, val_ctl2);
+ u64X = (u64) u32X;
+ u64X = ((u64X * 1000) >> 24);
+ u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
+ 1000 / u32P / u32M;
+ }
+ return u64PllClk;
+}
+
+static int ma35d1_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
+
+ if ((parent_rate < VSIPLL_FREF_MIN_FREQ) ||
+ (parent_rate > VSIPLL_FREF_MAX_FREQ))
+ return 0;
+
+ if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
+ pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
+ return -EACCES;
+ }
+ CLK_UnLockReg(pll);
+ pll->rate = CLK_SetPLLFreq(pll, parent_rate, rate);
+ CLK_LockReg(pll);
+ return 0;
+}
+
+static unsigned long ma35d1_clk_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ unsigned long pllfreq;
+ struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
+
+ if ((parent_rate < VSIPLL_FREF_MIN_FREQ)
+ || (parent_rate > VSIPLL_FREF_MAX_FREQ))
+ return 0;
+
+ switch (pll->type) {
+ case MA35D1_CAPLL:
+ pllfreq = CLK_GetPLLFreq_SMICPLL(pll, parent_rate);
+ break;
+ case MA35D1_DDRPLL:
+ case MA35D1_APLL:
+ case MA35D1_EPLL:
+ case MA35D1_VPLL:
+ pllfreq = CLK_GetPLLFreq_VSIPLL(pll, parent_rate);
+ break;
+ }
+
+ return pllfreq;
+}
+
+static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ return rate;
+}
+
+static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw)
+{
+ struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
+ u32 val = __raw_readl(pll->ctl1_base);
+
+ return (val & VSIPLLCTL1_PD_MSK) ? 0 : 1;
+}
+
+static int ma35d1_clk_pll_prepare(struct clk_hw *hw)
+{
+ struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
+ u32 val;
+
+ if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
+ pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
+ return -EACCES;
+ }
+
+ CLK_UnLockReg(pll);
+ val = __raw_readl(pll->ctl1_base);
+ val &= ~VSIPLLCTL1_PD_MSK;
+ __raw_writel(val, pll->ctl1_base);
+ CLK_LockReg(pll);
+ return 0;
+}
+
+static void ma35d1_clk_pll_unprepare(struct clk_hw *hw)
+{
+ struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
+ u32 val;
+
+ if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
+ pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
+ } else {
+ val = __raw_readl(pll->ctl1_base);
+ val |= VSIPLLCTL1_PD_MSK;
+ __raw_writel(val, pll->ctl1_base);
+ }
+}
+
+static const struct clk_ops ma35d1_clk_pll_ops = {
+ .is_prepared = ma35d1_clk_pll_is_prepared,
+ .prepare = ma35d1_clk_pll_prepare,
+ .unprepare = ma35d1_clk_pll_unprepare,
+ .set_rate = ma35d1_clk_pll_set_rate,
+ .recalc_rate = ma35d1_clk_pll_recalc_rate,
+ .round_rate = ma35d1_clk_pll_round_rate,
+};
+
+struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type,
+ u8 u8mode, const char *name,
+ const char *parent,
+ unsigned long targetFreq,
+ void __iomem *base,
+ struct regmap *regmap)
+{
+ struct ma35d1_clk_pll *pll;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ pll = kmalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->type = type;
+ pll->mode = u8mode;
+ pll->rate = targetFreq;
+ pll->ctl0_base = base + VSIPLL_CTL0;
+ pll->ctl1_base = base + VSIPLL_CTL1;
+ pll->ctl2_base = base + VSIPLL_CTL2;
+ pll->regmap = regmap;
+
+ init.name = name;
+ init.flags = 0;
+ init.parent_names = &parent;
+ init.num_parents = 1;
+ init.ops = &ma35d1_clk_pll_ops;
+ pll->hw.init = &init;
+ hw = &pll->hw;
+
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ pr_err("failed to register vsi-pll clock!!!\n");
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+ return hw;
+}
diff --git a/drivers/clk/nuvoton/clk-ma35d1.c b/drivers/clk/nuvoton/clk-ma35d1.c
new file mode 100644
index 000000000000..ac8154458b81
--- /dev/null
+++ b/drivers/clk/nuvoton/clk-ma35d1.c
@@ -0,0 +1,970 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Chi-Fang Li <[email protected]>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+#include "clk-ma35d1.h"
+
+DEFINE_SPINLOCK(ma35d1_lock);
+
+static const char *const ca35clk_sel_clks[] = {
+ "hxt", "capll", "ddrpll", "dummy"
+};
+
+static const char *const sysclk0_sel_clks[] = {
+ "epll_div2", "syspll"
+};
+
+static const char *const sysclk1_sel_clks[] = {
+ "hxt", "syspll"
+};
+
+static const char *const axiclk_sel_clks[] = {
+ "capll_div2", "capll_div4"
+};
+
+static const char *const ccap_sel_clks[] = {
+ "hxt", "vpll", "apll", "syspll"
+};
+
+static const char *const sdh_sel_clks[] = {
+ "syspll", "apll", "dummy", "dummy"
+};
+
+static const char *const dcu_sel_clks[] = {
+ "epll_div2", "syspll"
+};
+
+static const char *const gfx_sel_clks[] = {
+ "epll", "syspll"
+};
+
+static const char *const dbg_sel_clks[] = {
+ "hirc", "syspll"
+};
+
+static const char *const timer0_sel_clks[] = {
+ "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer1_sel_clks[] = {
+ "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer2_sel_clks[] = {
+ "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer3_sel_clks[] = {
+ "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer4_sel_clks[] = {
+ "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer5_sel_clks[] = {
+ "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer6_sel_clks[] = {
+ "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer7_sel_clks[] = {
+ "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer8_sel_clks[] = {
+ "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer9_sel_clks[] = {
+ "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer10_sel_clks[] = {
+ "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const timer11_sel_clks[] = {
+ "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
+};
+
+static const char *const uart_sel_clks[] = {
+ "hxt", "sysclk1_div2", "dummy", "dummy"
+};
+
+static const char *const wdt0_sel_clks[] = {
+ "dummy", "lxt", "pclk3_div4096", "lirc"
+};
+
+static const char *const wdt1_sel_clks[] = {
+ "dummy", "lxt", "pclk3_div4096", "lirc"
+};
+
+static const char *const wdt2_sel_clks[] = {
+ "dummy", "lxt", "pclk4_div4096", "lirc"
+};
+
+static const char *const wwdt0_sel_clks[] = {
+ "dummy", "dummy", "pclk3_div4096", "lirc"
+};
+
+static const char *const wwdt1_sel_clks[] = {
+ "dummy", "dummy", "pclk3_div4096", "lirc"
+};
+
+static const char *const wwdt2_sel_clks[] = {
+ "dummy", "dummy", "pclk4_div4096", "lirc"
+};
+
+static const char *const spi0_sel_clks[] = {
+ "pclk1", "apll", "dummy", "dummy"
+};
+
+static const char *const spi1_sel_clks[] = {
+ "pclk2", "apll", "dummy", "dummy"
+};
+
+static const char *const spi2_sel_clks[] = {
+ "pclk1", "apll", "dummy", "dummy"
+};
+
+static const char *const spi3_sel_clks[] = {
+ "pclk2", "apll", "dummy", "dummy"
+};
+
+static const char *const qspi0_sel_clks[] = {
+ "pclk0", "apll", "dummy", "dummy"
+};
+
+static const char *const qspi1_sel_clks[] = {
+ "pclk0", "apll", "dummy", "dummy"
+};
+
+static const char *const i2s0_sel_clks[] = {
+ "apll", "sysclk1_div2", "dummy", "dummy"
+};
+
+static const char *const i2s1_sel_clks[] = {
+ "apll", "sysclk1_div2", "dummy", "dummy"
+};
+
+static const char *const can_sel_clks[] = {
+ "apll", "vpll"
+};
+
+static const char *const cko_sel_clks[] = {
+ "hxt", "lxt", "hirc", "lirc", "capll_div4", "syspll",
+ "ddrpll", "epll_div2", "apll", "vpll", "dummy", "dummy",
+ "dummy", "dummy", "dummy", "dummy"
+};
+
+static const char *const smc_sel_clks[] = {
+ "hxt", "pclk4"
+};
+
+static const char *const kpi_sel_clks[] = {
+ "hxt", "lxt"
+};
+
+static const struct clk_div_table ip_div_table[] = {
+ {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
+ {5, 12}, {6, 14}, {7, 16}, {0, 0},
+};
+
+static const struct clk_div_table eadc_div_table[] = {
+ {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
+ {5, 12}, {6, 14}, {7, 16}, {8, 18},
+ {9, 20}, {10, 22}, {11, 24}, {12, 26},
+ {13, 28}, {14, 30}, {15, 32}, {0, 0},
+};
+
+static struct clk_hw **hws;
+static struct clk_hw_onecell_data *ma35d1_hw_data;
+
+static int ma35d1_clocks_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *clk_node = dev->of_node;
+ void __iomem *clk_base;
+ struct regmap *regmap;
+ u32 pllmode[5] = { 0, 0, 0, 0, 0 };
+ u32 pllfreq[5] = { 0, 0, 0, 0, 0 };
+
+ dev_info(&pdev->dev, "Nuvoton MA35D1 Clock Driver\n");
+ ma35d1_hw_data = devm_kzalloc(&pdev->dev, struct_size(ma35d1_hw_data,
+ hws, CLK_MAX_IDX), GFP_KERNEL);
+
+ if (WARN_ON(!ma35d1_hw_data))
+ return -ENOMEM;
+
+ ma35d1_hw_data->num = CLK_MAX_IDX;
+ hws = ma35d1_hw_data->hws;
+
+ clk_node = of_find_compatible_node(NULL, NULL, "nuvoton,ma35d1-clk");
+ clk_base = of_iomap(clk_node, 0);
+ of_node_put(clk_node);
+ if (!clk_base) {
+ pr_err("%s: could not map region\n", __func__);
+ return -ENOMEM;
+ }
+ regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "nuvoton,sys");
+ if (IS_ERR(regmap))
+ pr_warn("%s: Unable to get syscon\n", __func__);
+
+ /* clock sources */
+ hws[HXT] = ma35d1_clk_fixed("hxt", 24000000);
+ hws[HXT_GATE] = ma35d1_clk_gate("hxt_gate", "hxt",
+ clk_base + REG_CLK_PWRCTL, 0);
+ hws[LXT] = ma35d1_clk_fixed("lxt", 32768);
+ hws[LXT_GATE] = ma35d1_clk_gate("lxt_gate", "lxt",
+ clk_base + REG_CLK_PWRCTL, 1);
+ hws[HIRC] = ma35d1_clk_fixed("hirc", 12000000);
+ hws[HIRC_GATE] = ma35d1_clk_gate("hirc_gate", "hirc",
+ clk_base + REG_CLK_PWRCTL, 2);
+ hws[LIRC] = ma35d1_clk_fixed("lirc", 32000);
+ hws[LIRC_GATE] = ma35d1_clk_gate("lirc_gate", "lirc",
+ clk_base + REG_CLK_PWRCTL, 3);
+
+ /* PLL */
+ of_property_read_u32_array(clk_node, "clock-pll-mode", pllmode,
+ ARRAY_SIZE(pllmode));
+ of_property_read_u32_array(clk_node, "assigned-clock-rates", pllfreq,
+ ARRAY_SIZE(pllfreq));
+
+ /* SMIC PLL */
+ hws[CAPLL] = ma35d1_reg_clk_pll(MA35D1_CAPLL, pllmode[0], "capll",
+ "hxt", pllfreq[0],
+ clk_base + REG_CLK_PLL0CTL0, regmap);
+ hws[SYSPLL] = ma35d1_clk_fixed("syspll", 180000000);
+
+ /* VSI PLL */
+ hws[DDRPLL] = ma35d1_reg_clk_pll(MA35D1_DDRPLL, pllmode[1], "ddrpll",
+ "hxt", pllfreq[1],
+ clk_base + REG_CLK_PLL2CTL0, regmap);
+ hws[APLL] = ma35d1_reg_clk_pll(MA35D1_APLL, pllmode[2], "apll", "hxt",
+ pllfreq[2], clk_base + REG_CLK_PLL3CTL0,
+ regmap);
+ hws[EPLL] = ma35d1_reg_clk_pll(MA35D1_EPLL, pllmode[3], "epll", "hxt",
+ pllfreq[3], clk_base + REG_CLK_PLL4CTL0,
+ regmap);
+ hws[VPLL] = ma35d1_reg_clk_pll(MA35D1_VPLL, pllmode[4], "vpll", "hxt",
+ pllfreq[4], clk_base + REG_CLK_PLL5CTL0,
+ regmap);
+ hws[EPLL_DIV2] = ma35d1_clk_fixed_factor("epll_div2", "epll", 1, 2);
+ hws[EPLL_DIV4] = ma35d1_clk_fixed_factor("epll_div4", "epll", 1, 4);
+ hws[EPLL_DIV8] = ma35d1_clk_fixed_factor("epll_div8", "epll", 1, 8);
+
+ /* CA35 */
+ hws[CA35CLK_MUX] = ma35d1_clk_mux("ca35clk_mux",
+ clk_base + REG_CLK_CLKSEL0, 0,
+ 2, ca35clk_sel_clks,
+ ARRAY_SIZE(ca35clk_sel_clks));
+
+ /* AXI */
+ hws[AXICLK_DIV2] = ma35d1_clk_fixed_factor("capll_div2", "ca35clk_mux",
+ 1, 2);
+ hws[AXICLK_DIV4] = ma35d1_clk_fixed_factor("capll_div4", "ca35clk_mux",
+ 1, 4);
+ hws[AXICLK_MUX] = ma35d1_clk_mux("axiclk_mux",
+ clk_base + REG_CLK_CLKDIV0,
+ 26, 1, axiclk_sel_clks,
+ ARRAY_SIZE(axiclk_sel_clks));
+
+ /* SYSCLK0 & SYSCLK1 */
+ hws[SYSCLK0_MUX] = ma35d1_clk_mux("sysclk0_mux",
+ clk_base + REG_CLK_CLKSEL0,
+ 2, 1, sysclk0_sel_clks,
+ ARRAY_SIZE(sysclk0_sel_clks));
+ hws[SYSCLK1_MUX] = ma35d1_clk_mux("sysclk1_mux",
+ clk_base + REG_CLK_CLKSEL0,
+ 4, 1, sysclk1_sel_clks,
+ ARRAY_SIZE(sysclk1_sel_clks));
+ hws[SYSCLK1_DIV2] = ma35d1_clk_fixed_factor("sysclk1_div2",
+ "sysclk1_mux", 1, 2);
+
+ /* HCLK0~3 & PCLK0~4 */
+ hws[HCLK0] = ma35d1_clk_fixed_factor("hclk0", "sysclk1_mux", 1, 1);
+ hws[HCLK1] = ma35d1_clk_fixed_factor("hclk1", "sysclk1_mux", 1, 1);
+ hws[HCLK2] = ma35d1_clk_fixed_factor("hclk2", "sysclk1_mux", 1, 1);
+ hws[PCLK0] = ma35d1_clk_fixed_factor("pclk0", "sysclk1_mux", 1, 1);
+ hws[PCLK1] = ma35d1_clk_fixed_factor("pclk1", "sysclk1_mux", 1, 1);
+ hws[PCLK2] = ma35d1_clk_fixed_factor("pclk2", "sysclk1_mux", 1, 1);
+
+ hws[HCLK3] = ma35d1_clk_fixed_factor("hclk3", "sysclk1_mux", 1, 2);
+ hws[PCLK3] = ma35d1_clk_fixed_factor("pclk3", "sysclk1_mux", 1, 2);
+ hws[PCLK4] = ma35d1_clk_fixed_factor("pclk4", "sysclk1_mux", 1, 2);
+
+ hws[USBPHY0] = ma35d1_clk_fixed("usbphy0", 480000000);
+ hws[USBPHY1] = ma35d1_clk_fixed("usbphy1", 480000000);
+
+ /* DDR */
+ hws[DDR0_GATE] = ma35d1_clk_gate("ddr0_gate", "ddrpll",
+ clk_base + REG_CLK_SYSCLK0, 4);
+ hws[DDR6_GATE] = ma35d1_clk_gate("ddr6_gate", "ddrpll",
+ clk_base + REG_CLK_SYSCLK0, 5);
+
+ /* CAN0 */
+ hws[CAN0_MUX] = ma35d1_clk_mux("can0_mux", clk_base + REG_CLK_CLKSEL4,
+ 16, 1, can_sel_clks,
+ ARRAY_SIZE(can_sel_clks));
+ hws[CAN0_DIV] = ma35d1_clk_divider_table("can0_div", "can0_mux",
+ clk_base + REG_CLK_CLKDIV0,
+ 0, 3, ip_div_table);
+ hws[CAN0_GATE] = ma35d1_clk_gate("can0_gate", "can0_div",
+ clk_base + REG_CLK_SYSCLK0, 8);
+
+ /* CAN1 */
+ hws[CAN1_MUX] = ma35d1_clk_mux("can1_mux", clk_base + REG_CLK_CLKSEL4,
+ 17, 1, can_sel_clks,
+ ARRAY_SIZE(can_sel_clks));
+ hws[CAN1_DIV] = ma35d1_clk_divider_table("can1_div", "can1_mux",
+ clk_base + REG_CLK_CLKDIV0,
+ 4, 3, ip_div_table);
+ hws[CAN1_GATE] = ma35d1_clk_gate("can1_gate", "can1_div",
+ clk_base + REG_CLK_SYSCLK0, 9);
+
+ /* CAN2 */
+ hws[CAN2_MUX] = ma35d1_clk_mux("can2_mux", clk_base + REG_CLK_CLKSEL4,
+ 18, 1, can_sel_clks,
+ ARRAY_SIZE(can_sel_clks));
+ hws[CAN2_DIV] = ma35d1_clk_divider_table("can2_div", "can2_mux",
+ clk_base + REG_CLK_CLKDIV0,
+ 8, 3, ip_div_table);
+ hws[CAN2_GATE] = ma35d1_clk_gate("can2_gate", "can2_div",
+ clk_base + REG_CLK_SYSCLK0, 10);
+
+ /* CAN3 */
+ hws[CAN3_MUX] = ma35d1_clk_mux("can3_mux", clk_base + REG_CLK_CLKSEL4,
+ 19, 1, can_sel_clks,
+ ARRAY_SIZE(can_sel_clks));
+ hws[CAN3_DIV] = ma35d1_clk_divider_table("can3_div", "can3_mux",
+ clk_base + REG_CLK_CLKDIV0,
+ 12, 3, ip_div_table);
+ hws[CAN3_GATE] = ma35d1_clk_gate("can3_gate", "can3_div",
+ clk_base + REG_CLK_SYSCLK0, 11);
+
+ /* SDH0 */
+ hws[SDH0_MUX] = ma35d1_clk_mux("sdh0_mux", clk_base + REG_CLK_CLKSEL0,
+ 16, 2, sdh_sel_clks,
+ ARRAY_SIZE(sdh_sel_clks));
+ hws[SDH0_GATE] = ma35d1_clk_gate("sdh0_gate", "sdh0_mux",
+ clk_base + REG_CLK_SYSCLK0, 16);
+
+ /* SDH1 */
+ hws[SDH1_MUX] = ma35d1_clk_mux("sdh1_mux", clk_base + REG_CLK_CLKSEL0,
+ 18, 2, sdh_sel_clks,
+ ARRAY_SIZE(sdh_sel_clks));
+ hws[SDH1_GATE] = ma35d1_clk_gate("sdh1_gate", "sdh1_mux",
+ clk_base + REG_CLK_SYSCLK0, 17);
+
+ /* NAND */
+ hws[NAND_GATE] = ma35d1_clk_gate("nand_gate", "hclk1",
+ clk_base + REG_CLK_SYSCLK0, 18);
+
+ /* USB */
+ hws[USBD_GATE] = ma35d1_clk_gate("usbd_gate", "usbphy0",
+ clk_base + REG_CLK_SYSCLK0, 19);
+ hws[USBH_GATE] = ma35d1_clk_gate("usbh_gate", "usbphy0",
+ clk_base + REG_CLK_SYSCLK0, 20);
+ hws[HUSBH0_GATE] = ma35d1_clk_gate("husbh0_gate", "usbphy0",
+ clk_base + REG_CLK_SYSCLK0, 21);
+ hws[HUSBH1_GATE] = ma35d1_clk_gate("husbh1_gate", "usbphy0",
+ clk_base + REG_CLK_SYSCLK0, 22);
+
+ /* GFX */
+ hws[GFX_MUX] = ma35d1_clk_mux("gfx_mux", clk_base + REG_CLK_CLKSEL0,
+ 26, 1, gfx_sel_clks,
+ ARRAY_SIZE(gfx_sel_clks));
+ hws[GFX_GATE] = ma35d1_clk_gate("gfx_gate", "gfx_mux",
+ clk_base + REG_CLK_SYSCLK0, 24);
+
+ /* VC8K */
+ hws[VC8K_GATE] = ma35d1_clk_gate("vc8k_gate", "sysclk0_mux",
+ clk_base + REG_CLK_SYSCLK0, 25);
+
+ /* DCU */
+ hws[DCU_MUX] = ma35d1_clk_mux("dcu_mux", clk_base + REG_CLK_CLKSEL0,
+ 24, 1, dcu_sel_clks,
+ ARRAY_SIZE(dcu_sel_clks));
+ hws[DCU_GATE] = ma35d1_clk_gate("dcu_gate", "dcu_mux",
+ clk_base + REG_CLK_SYSCLK0, 26);
+
+ /* DCUP */
+ hws[DCUP_DIV] = ma35d1_clk_divider_table("dcup_div", "vpll",
+ clk_base + REG_CLK_CLKDIV0,
+ 16, 3, ip_div_table);
+
+ /* EMAC0 */
+ hws[EMAC0_GATE] = ma35d1_clk_gate("emac0_gate", "epll_div2",
+ clk_base + REG_CLK_SYSCLK0, 27);
+
+ /* EMAC1 */
+ hws[EMAC1_GATE] = ma35d1_clk_gate("emac1_gate", "epll_div2",
+ clk_base + REG_CLK_SYSCLK0, 28);
+
+ /* CCAP0 */
+ hws[CCAP0_MUX] = ma35d1_clk_mux("ccap0_mux",
+ clk_base + REG_CLK_CLKSEL0,
+ 12, 1, ccap_sel_clks,
+ ARRAY_SIZE(ccap_sel_clks));
+ hws[CCAP0_DIV] = ma35d1_clk_divider("ccap0_div", "ccap0_mux",
+ clk_base + REG_CLK_CLKDIV1, 8, 4);
+ hws[CCAP0_GATE] = ma35d1_clk_gate("ccap0_gate", "ccap0_div",
+ clk_base + REG_CLK_SYSCLK0, 29);
+
+ /* CCAP1 */
+ hws[CCAP1_MUX] = ma35d1_clk_mux("ccap1_mux",
+ clk_base + REG_CLK_CLKSEL0,
+ 14, 1, ccap_sel_clks,
+ ARRAY_SIZE(ccap_sel_clks));
+ hws[CCAP1_DIV] = ma35d1_clk_divider("ccap1_div", "ccap1_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 12, 4);
+ hws[CCAP1_GATE] = ma35d1_clk_gate("ccap1_gate", "ccap1_div",
+ clk_base + REG_CLK_SYSCLK0, 30);
+
+ /* PDMA0~3 */
+ hws[PDMA0_GATE] = ma35d1_clk_gate("pdma0_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 0);
+ hws[PDMA1_GATE] = ma35d1_clk_gate("pdma1_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 1);
+ hws[PDMA2_GATE] = ma35d1_clk_gate("pdma2_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 2);
+ hws[PDMA3_GATE] = ma35d1_clk_gate("pdma3_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 3);
+
+ /* WH0~1 */
+ hws[WH0_GATE] = ma35d1_clk_gate("wh0_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 4);
+ hws[WH1_GATE] = ma35d1_clk_gate("wh1_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 5);
+
+ /* HWS */
+ hws[HWS_GATE] = ma35d1_clk_gate("hws_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 6);
+
+ /* EBI */
+ hws[EBI_GATE] = ma35d1_clk_gate("ebi_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 7);
+
+ /* SRAM0~1 */
+ hws[SRAM0_GATE] = ma35d1_clk_gate("sram0_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 8);
+ hws[SRAM1_GATE] = ma35d1_clk_gate("sram1_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 9);
+
+ /* ROM */
+ hws[ROM_GATE] = ma35d1_clk_gate("rom_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 10);
+
+ /* TRA */
+ hws[TRA_GATE] = ma35d1_clk_gate("tra_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 11);
+
+ /* DBG */
+ hws[DBG_MUX] = ma35d1_clk_mux("dbg_mux", clk_base + REG_CLK_CLKSEL0,
+ 27, 1, dbg_sel_clks,
+ ARRAY_SIZE(dbg_sel_clks));
+ hws[DBG_GATE] = ma35d1_clk_gate("dbg_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 12);
+
+ /* CLKO */
+ hws[CKO_MUX] = ma35d1_clk_mux("cko_mux", clk_base + REG_CLK_CLKSEL4,
+ 24, 4, cko_sel_clks,
+ ARRAY_SIZE(cko_sel_clks));
+ hws[CKO_DIV] = ma35d1_clk_divider_pow2("cko_div", "cko_mux",
+ clk_base + REG_CLK_CLKOCTL,
+ 0, 4);
+ hws[CKO_GATE] = ma35d1_clk_gate("cko_gate", "cko_div",
+ clk_base + REG_CLK_SYSCLK1, 13);
+
+ /* GTMR */
+ hws[GTMR_GATE] = ma35d1_clk_gate("gtmr_gate", "hirc",
+ clk_base + REG_CLK_SYSCLK1, 14);
+
+ /* GPIO */
+ hws[GPA_GATE] = ma35d1_clk_gate("gpa_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 16);
+ hws[GPB_GATE] = ma35d1_clk_gate("gpb_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 17);
+ hws[GPC_GATE] = ma35d1_clk_gate("gpc_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 18);
+ hws[GPD_GATE] = ma35d1_clk_gate("gpd_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 19);
+ hws[GPE_GATE] = ma35d1_clk_gate("gpe_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 20);
+ hws[GPF_GATE] = ma35d1_clk_gate("gpf_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 21);
+ hws[GPG_GATE] = ma35d1_clk_gate("gpg_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 22);
+ hws[GPH_GATE] = ma35d1_clk_gate("gph_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 23);
+ hws[GPI_GATE] = ma35d1_clk_gate("gpi_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 24);
+ hws[GPJ_GATE] = ma35d1_clk_gate("gpj_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 25);
+ hws[GPK_GATE] = ma35d1_clk_gate("gpk_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 26);
+ hws[GPL_GATE] = ma35d1_clk_gate("gpl_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 27);
+ hws[GPM_GATE] = ma35d1_clk_gate("gpm_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 28);
+ hws[GPN_GATE] = ma35d1_clk_gate("gpn_gate", "hclk0",
+ clk_base + REG_CLK_SYSCLK1, 29);
+
+ /* TIMER0~11 */
+ hws[TMR0_MUX] = ma35d1_clk_mux("tmr0_mux", clk_base + REG_CLK_CLKSEL1,
+ 0, 3, timer0_sel_clks,
+ ARRAY_SIZE(timer0_sel_clks));
+ hws[TMR0_GATE] = ma35d1_clk_gate("tmr0_gate", "tmr0_mux",
+ clk_base + REG_CLK_APBCLK0, 0);
+ hws[TMR1_MUX] = ma35d1_clk_mux("tmr1_mux", clk_base + REG_CLK_CLKSEL1,
+ 4, 3, timer1_sel_clks,
+ ARRAY_SIZE(timer1_sel_clks));
+ hws[TMR1_GATE] = ma35d1_clk_gate("tmr1_gate", "tmr1_mux",
+ clk_base + REG_CLK_APBCLK0, 1);
+ hws[TMR2_MUX] = ma35d1_clk_mux("tmr2_mux", clk_base + REG_CLK_CLKSEL1,
+ 8, 3, timer2_sel_clks,
+ ARRAY_SIZE(timer2_sel_clks));
+ hws[TMR2_GATE] = ma35d1_clk_gate("tmr2_gate", "tmr2_mux",
+ clk_base + REG_CLK_APBCLK0, 2);
+ hws[TMR3_MUX] = ma35d1_clk_mux("tmr3_mux", clk_base + REG_CLK_CLKSEL1,
+ 12, 3, timer3_sel_clks,
+ ARRAY_SIZE(timer3_sel_clks));
+ hws[TMR3_GATE] = ma35d1_clk_gate("tmr3_gate", "tmr3_mux",
+ clk_base + REG_CLK_APBCLK0, 3);
+ hws[TMR4_MUX] = ma35d1_clk_mux("tmr4_mux", clk_base + REG_CLK_CLKSEL1,
+ 16, 3, timer4_sel_clks,
+ ARRAY_SIZE(timer4_sel_clks));
+ hws[TMR4_GATE] = ma35d1_clk_gate("tmr4_gate", "tmr4_mux",
+ clk_base + REG_CLK_APBCLK0, 4);
+ hws[TMR5_MUX] = ma35d1_clk_mux("tmr5_mux", clk_base + REG_CLK_CLKSEL1,
+ 20, 3, timer5_sel_clks,
+ ARRAY_SIZE(timer5_sel_clks));
+ hws[TMR5_GATE] = ma35d1_clk_gate("tmr5_gate", "tmr5_mux",
+ clk_base + REG_CLK_APBCLK0, 5);
+ hws[TMR6_MUX] = ma35d1_clk_mux("tmr6_mux", clk_base + REG_CLK_CLKSEL1,
+ 24, 3, timer6_sel_clks,
+ ARRAY_SIZE(timer6_sel_clks));
+ hws[TMR6_GATE] = ma35d1_clk_gate("tmr6_gate", "tmr6_mux",
+ clk_base + REG_CLK_APBCLK0, 6);
+ hws[TMR7_MUX] = ma35d1_clk_mux("tmr7_mux", clk_base + REG_CLK_CLKSEL1,
+ 28, 3, timer7_sel_clks,
+ ARRAY_SIZE(timer7_sel_clks));
+ hws[TMR7_GATE] = ma35d1_clk_gate("tmr7_gate", "tmr7_mux",
+ clk_base + REG_CLK_APBCLK0, 7);
+ hws[TMR8_MUX] = ma35d1_clk_mux("tmr8_mux", clk_base + REG_CLK_CLKSEL2,
+ 0, 3, timer8_sel_clks,
+ ARRAY_SIZE(timer8_sel_clks));
+ hws[TMR8_GATE] = ma35d1_clk_gate("tmr8_gate", "tmr8_mux",
+ clk_base + REG_CLK_APBCLK0, 8);
+ hws[TMR9_MUX] = ma35d1_clk_mux("tmr9_mux", clk_base + REG_CLK_CLKSEL2,
+ 4, 3, timer9_sel_clks,
+ ARRAY_SIZE(timer9_sel_clks));
+ hws[TMR9_GATE] = ma35d1_clk_gate("tmr9_gate", "tmr9_mux",
+ clk_base + REG_CLK_APBCLK0, 9);
+ hws[TMR10_MUX] = ma35d1_clk_mux("tmr10_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 8, 3, timer10_sel_clks,
+ ARRAY_SIZE(timer10_sel_clks));
+ hws[TMR10_GATE] = ma35d1_clk_gate("tmr10_gate", "tmr10_mux",
+ clk_base + REG_CLK_APBCLK0, 10);
+ hws[TMR11_MUX] = ma35d1_clk_mux("tmr11_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 12, 3, timer11_sel_clks,
+ ARRAY_SIZE(timer11_sel_clks));
+ hws[TMR11_GATE] = ma35d1_clk_gate("tmr11_gate", "tmr11_mux",
+ clk_base + REG_CLK_APBCLK0, 11);
+
+ /* UART0~16 */
+ hws[UART0_MUX] = ma35d1_clk_mux("uart0_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 16, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART0_DIV] = ma35d1_clk_divider("uart0_div", "uart0_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 16, 4);
+ hws[UART0_GATE] = ma35d1_clk_gate("uart0_gate", "uart0_div",
+ clk_base + REG_CLK_APBCLK0, 12);
+ hws[UART1_MUX] = ma35d1_clk_mux("uart1_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 18, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART1_DIV] = ma35d1_clk_divider("uart1_div", "uart1_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 20, 4);
+ hws[UART1_GATE] = ma35d1_clk_gate("uart1_gate", "uart1_div",
+ clk_base + REG_CLK_APBCLK0, 13);
+ hws[UART2_MUX] = ma35d1_clk_mux("uart2_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 20, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART2_DIV] = ma35d1_clk_divider("uart2_div", "uart2_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 24, 4);
+ hws[UART2_GATE] = ma35d1_clk_gate("uart2_gate", "uart2_div",
+ clk_base + REG_CLK_APBCLK0, 14);
+ hws[UART3_MUX] = ma35d1_clk_mux("uart3_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 22, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART3_DIV] = ma35d1_clk_divider("uart3_div", "uart3_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 28, 4);
+ hws[UART3_GATE] = ma35d1_clk_gate("uart3_gate", "uart3_div",
+ clk_base + REG_CLK_APBCLK0, 15);
+ hws[UART4_MUX] = ma35d1_clk_mux("uart4_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 24, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART4_DIV] = ma35d1_clk_divider("uart4_div", "uart4_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 0, 4);
+ hws[UART4_GATE] = ma35d1_clk_gate("uart4_gate", "uart4_div",
+ clk_base + REG_CLK_APBCLK0, 16);
+ hws[UART5_MUX] = ma35d1_clk_mux("uart5_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 26, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART5_DIV] = ma35d1_clk_divider("uart5_div", "uart5_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 4, 4);
+ hws[UART5_GATE] = ma35d1_clk_gate("uart5_gate", "uart5_div",
+ clk_base + REG_CLK_APBCLK0, 17);
+ hws[UART6_MUX] = ma35d1_clk_mux("uart6_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 28, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART6_DIV] = ma35d1_clk_divider("uart6_div", "uart6_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 8, 4);
+ hws[UART6_GATE] = ma35d1_clk_gate("uart6_gate", "uart6_div",
+ clk_base + REG_CLK_APBCLK0, 18);
+ hws[UART7_MUX] = ma35d1_clk_mux("uart7_mux",
+ clk_base + REG_CLK_CLKSEL2,
+ 30, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART7_DIV] = ma35d1_clk_divider("uart7_div", "uart7_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 12, 4);
+ hws[UART7_GATE] = ma35d1_clk_gate("uart7_gate", "uart7_div",
+ clk_base + REG_CLK_APBCLK0, 19);
+ hws[UART8_MUX] = ma35d1_clk_mux("uart8_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 0, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART8_DIV] = ma35d1_clk_divider("uart8_div", "uart8_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 16, 4);
+ hws[UART8_GATE] = ma35d1_clk_gate("uart8_gate", "uart8_div",
+ clk_base + REG_CLK_APBCLK0, 20);
+ hws[UART9_MUX] = ma35d1_clk_mux("uart9_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 2, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART9_DIV] = ma35d1_clk_divider("uart9_div", "uart9_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 20, 4);
+ hws[UART9_GATE] = ma35d1_clk_gate("uart9_gate", "uart9_div",
+ clk_base + REG_CLK_APBCLK0, 21);
+ hws[UART10_MUX] = ma35d1_clk_mux("uart10_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 4, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART10_DIV] = ma35d1_clk_divider("uart10_div", "uart10_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 24, 4);
+ hws[UART10_GATE] = ma35d1_clk_gate("uart10_gate", "uart10_div",
+ clk_base + REG_CLK_APBCLK0, 22);
+ hws[UART11_MUX] = ma35d1_clk_mux("uart11_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 6, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART11_DIV] = ma35d1_clk_divider("uart11_div", "uart11_mux",
+ clk_base + REG_CLK_CLKDIV2,
+ 28, 4);
+ hws[UART11_GATE] = ma35d1_clk_gate("uart11_gate", "uart11_div",
+ clk_base + REG_CLK_APBCLK0, 23);
+ hws[UART12_MUX] = ma35d1_clk_mux("uart12_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 8, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART12_DIV] = ma35d1_clk_divider("uart12_div", "uart12_mux",
+ clk_base + REG_CLK_CLKDIV3,
+ 0, 4);
+ hws[UART12_GATE] = ma35d1_clk_gate("uart12_gate", "uart12_div",
+ clk_base + REG_CLK_APBCLK0, 24);
+ hws[UART13_MUX] = ma35d1_clk_mux("uart13_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 10, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART13_DIV] = ma35d1_clk_divider("uart13_div", "uart13_mux",
+ clk_base + REG_CLK_CLKDIV3,
+ 4, 4);
+ hws[UART13_GATE] = ma35d1_clk_gate("uart13_gate", "uart13_div",
+ clk_base + REG_CLK_APBCLK0, 25);
+ hws[UART14_MUX] = ma35d1_clk_mux("uart14_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 12, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART14_DIV] = ma35d1_clk_divider("uart14_div", "uart14_mux",
+ clk_base + REG_CLK_CLKDIV3,
+ 8, 4);
+ hws[UART14_GATE] = ma35d1_clk_gate("uart14_gate", "uart14_div",
+ clk_base + REG_CLK_APBCLK0, 26);
+ hws[UART15_MUX] = ma35d1_clk_mux("uart15_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 14, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART15_DIV] = ma35d1_clk_divider("uart15_div", "uart15_mux",
+ clk_base + REG_CLK_CLKDIV3,
+ 12, 4);
+ hws[UART15_GATE] = ma35d1_clk_gate("uart15_gate", "uart15_div",
+ clk_base + REG_CLK_APBCLK0, 27);
+ hws[UART16_MUX] = ma35d1_clk_mux("uart16_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 16, 2, uart_sel_clks,
+ ARRAY_SIZE(uart_sel_clks));
+ hws[UART16_DIV] = ma35d1_clk_divider("uart16_div", "uart16_mux",
+ clk_base + REG_CLK_CLKDIV3,
+ 16, 4);
+ hws[UART16_GATE] = ma35d1_clk_gate("uart16_gate", "uart16_div",
+ clk_base + REG_CLK_APBCLK0, 28);
+
+ /* RTC */
+ hws[RTC_GATE] = ma35d1_clk_gate("rtc_gate", "lxt",
+ clk_base + REG_CLK_APBCLK0, 29);
+
+ /* DDRP */
+ hws[DDR_GATE] = ma35d1_clk_gate("ddr_gate", "ddrpll",
+ clk_base + REG_CLK_APBCLK0, 30);
+
+ /* KPI */
+ hws[KPI_MUX] = ma35d1_clk_mux("kpi_mux", clk_base + REG_CLK_CLKSEL4,
+ 30, 1, kpi_sel_clks,
+ ARRAY_SIZE(kpi_sel_clks));
+ hws[KPI_DIV] = ma35d1_clk_divider("kpi_div", "kpi_mux",
+ clk_base + REG_CLK_CLKDIV4,
+ 24, 8);
+ hws[KPI_GATE] = ma35d1_clk_gate("kpi_gate", "kpi_div",
+ clk_base + REG_CLK_APBCLK0, 31);
+
+ /* I2C0~5 */
+ hws[I2C0_GATE] = ma35d1_clk_gate("i2c0_gate", "pclk0",
+ clk_base + REG_CLK_APBCLK1, 0);
+ hws[I2C1_GATE] = ma35d1_clk_gate("i2c1_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK1, 1);
+ hws[I2C2_GATE] = ma35d1_clk_gate("i2c2_gate", "pclk2",
+ clk_base + REG_CLK_APBCLK1, 2);
+ hws[I2C3_GATE] = ma35d1_clk_gate("i2c3_gate", "pclk0",
+ clk_base + REG_CLK_APBCLK1, 3);
+ hws[I2C4_GATE] = ma35d1_clk_gate("i2c4_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK1, 4);
+ hws[I2C5_GATE] = ma35d1_clk_gate("i2c5_gate", "pclk2",
+ clk_base + REG_CLK_APBCLK1, 5);
+
+ /* QSPI0~1 */
+ hws[QSPI0_MUX] = ma35d1_clk_mux("qspi0_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 8, 2, qspi0_sel_clks,
+ ARRAY_SIZE(qspi0_sel_clks));
+ hws[QSPI0_GATE] = ma35d1_clk_gate("qspi0_gate", "qspi0_mux",
+ clk_base + REG_CLK_APBCLK1, 6);
+ hws[QSPI1_MUX] = ma35d1_clk_mux("qspi1_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 10, 2, qspi1_sel_clks,
+ ARRAY_SIZE(qspi1_sel_clks));
+ hws[QSPI1_GATE] = ma35d1_clk_gate("qspi1_gate", "qspi1_mux",
+ clk_base + REG_CLK_APBCLK1, 7);
+
+ /* SMC0~1 */
+ hws[SMC0_MUX] = ma35d1_clk_mux("smc0_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 28, 1, smc_sel_clks,
+ ARRAY_SIZE(smc_sel_clks));
+ hws[SMC0_DIV] = ma35d1_clk_divider("smc0_div", "smc0_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 0, 4);
+ hws[SMC0_GATE] = ma35d1_clk_gate("smc0_gate", "smc0_div",
+ clk_base + REG_CLK_APBCLK1, 12);
+
+ hws[SMC1_MUX] = ma35d1_clk_mux("smc1_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 29, 1, smc_sel_clks,
+ ARRAY_SIZE(smc_sel_clks));
+ hws[SMC1_DIV] = ma35d1_clk_divider("smc1_div", "smc1_mux",
+ clk_base + REG_CLK_CLKDIV1,
+ 4, 4);
+ hws[SMC1_GATE] = ma35d1_clk_gate("smc1_gate", "smc1_div",
+ clk_base + REG_CLK_APBCLK1, 13);
+
+ /* WDT0~2 */
+ hws[WDT0_MUX] = ma35d1_clk_mux("wdt0_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 20, 2, wdt0_sel_clks,
+ ARRAY_SIZE(wdt0_sel_clks));
+ hws[WDT0_GATE] = ma35d1_clk_gate("wdt0_gate", "wdt0_mux",
+ clk_base + REG_CLK_APBCLK1, 16);
+ hws[WDT1_MUX] = ma35d1_clk_mux("wdt1_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 24, 2, wdt1_sel_clks,
+ ARRAY_SIZE(wdt1_sel_clks));
+ hws[WDT1_GATE] = ma35d1_clk_gate("wdt1_gate", "wdt1_mux",
+ clk_base + REG_CLK_APBCLK1, 17);
+ hws[WDT2_MUX] = ma35d1_clk_mux("wdt2_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 28, 2, wdt2_sel_clks,
+ ARRAY_SIZE(wdt2_sel_clks));
+ hws[WDT2_GATE] = ma35d1_clk_gate("wdt2_gate", "wdt2_mux",
+ clk_base + REG_CLK_APBCLK1, 18);
+
+ /* WWDT0~2 */
+ hws[WWDT0_MUX] = ma35d1_clk_mux("wwdt0_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 22, 2, wwdt0_sel_clks,
+ ARRAY_SIZE(wwdt0_sel_clks));
+ hws[WWDT1_MUX] = ma35d1_clk_mux("wwdt1_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 26, 2, wwdt1_sel_clks,
+ ARRAY_SIZE(wwdt1_sel_clks));
+ hws[WWDT2_MUX] = ma35d1_clk_mux("wwdt2_mux",
+ clk_base + REG_CLK_CLKSEL3,
+ 30, 2, wwdt2_sel_clks,
+ ARRAY_SIZE(wwdt2_sel_clks));
+
+ /* EPWM0~2 */
+ hws[EPWM0_GATE] = ma35d1_clk_gate("epwm0_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK1, 24);
+ hws[EPWM1_GATE] = ma35d1_clk_gate("epwm1_gate", "pclk2",
+ clk_base + REG_CLK_APBCLK1, 25);
+ hws[EPWM2_GATE] = ma35d1_clk_gate("epwm2_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK1, 26);
+
+ /* I2S0~1 */
+ hws[I2S0_MUX] = ma35d1_clk_mux("i2s0_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 12, 2, i2s0_sel_clks,
+ ARRAY_SIZE(i2s0_sel_clks));
+ hws[I2S0_GATE] = ma35d1_clk_gate("i2s0_gate", "i2s0_mux",
+ clk_base + REG_CLK_APBCLK2, 0);
+ hws[I2S1_MUX] = ma35d1_clk_mux("i2s1_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 14, 2, i2s1_sel_clks,
+ ARRAY_SIZE(i2s1_sel_clks));
+ hws[I2S1_GATE] = ma35d1_clk_gate("i2s1_gate", "i2s1_mux",
+ clk_base + REG_CLK_APBCLK2, 1);
+
+ /* SSMCC */
+ hws[SSMCC_GATE] = ma35d1_clk_gate("ssmcc_gate", "pclk3",
+ clk_base + REG_CLK_APBCLK2, 2);
+
+ /* SSPCC */
+ hws[SSPCC_GATE] = ma35d1_clk_gate("sspcc_gate", "pclk3",
+ clk_base + REG_CLK_APBCLK2, 3);
+
+ /* SPI0~3 */
+ hws[SPI0_MUX] = ma35d1_clk_mux("spi0_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 0, 2, spi0_sel_clks,
+ ARRAY_SIZE(spi0_sel_clks));
+ hws[SPI0_GATE] = ma35d1_clk_gate("spi0_gate", "spi0_mux",
+ clk_base + REG_CLK_APBCLK2, 4);
+ hws[SPI1_MUX] = ma35d1_clk_mux("spi1_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 2, 2, spi1_sel_clks,
+ ARRAY_SIZE(spi1_sel_clks));
+ hws[SPI1_GATE] = ma35d1_clk_gate("spi1_gate", "spi1_mux",
+ clk_base + REG_CLK_APBCLK2, 5);
+ hws[SPI2_MUX] = ma35d1_clk_mux("spi2_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 4, 2, spi2_sel_clks,
+ ARRAY_SIZE(spi2_sel_clks));
+ hws[SPI2_GATE] = ma35d1_clk_gate("spi2_gate", "spi2_mux",
+ clk_base + REG_CLK_APBCLK2, 6);
+ hws[SPI3_MUX] = ma35d1_clk_mux("spi3_mux",
+ clk_base + REG_CLK_CLKSEL4,
+ 6, 2, spi3_sel_clks,
+ ARRAY_SIZE(spi3_sel_clks));
+ hws[SPI3_GATE] = ma35d1_clk_gate("spi3_gate", "spi3_mux",
+ clk_base + REG_CLK_APBCLK2, 7);
+
+ /* ECAP0~2 */
+ hws[ECAP0_GATE] = ma35d1_clk_gate("ecap0_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK2, 8);
+ hws[ECAP1_GATE] = ma35d1_clk_gate("ecap1_gate", "pclk2",
+ clk_base + REG_CLK_APBCLK2, 9);
+ hws[ECAP2_GATE] = ma35d1_clk_gate("ecap2_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK2, 10);
+
+ /* QEI0~2 */
+ hws[QEI0_GATE] = ma35d1_clk_gate("qei0_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK2, 12);
+ hws[QEI1_GATE] = ma35d1_clk_gate("qei1_gate", "pclk2",
+ clk_base + REG_CLK_APBCLK2, 13);
+ hws[QEI2_GATE] = ma35d1_clk_gate("qei2_gate", "pclk1",
+ clk_base + REG_CLK_APBCLK2, 14);
+
+ /* ADC */
+ hws[ADC_DIV] = ma35d1_reg_adc_clkdiv(dev, "adc_div", "pclk0", 0,
+ clk_base + REG_CLK_CLKDIV4,
+ 4, 17, 0x1ffff);
+ hws[ADC_GATE] = ma35d1_clk_gate("adc_gate", "adc_div",
+ clk_base + REG_CLK_APBCLK2, 24);
+
+ /* EADC */
+ hws[EADC_DIV] = ma35d1_clk_divider_table("eadc_div", "pclk2",
+ clk_base + REG_CLK_CLKDIV4,
+ 0, 4, eadc_div_table);
+ hws[EADC_GATE] = ma35d1_clk_gate("eadc_gate", "eadc_div",
+ clk_base + REG_CLK_APBCLK2, 25);
+
+ ret = of_clk_add_hw_provider(clk_node, of_clk_hw_onecell_get,
+ ma35d1_hw_data);
+ if (ret < 0) {
+ dev_err(dev, "failed to register hws for MA35D1\n");
+ iounmap(clk_base);
+ }
+ return ret;
+}
+
+static const struct of_device_id ma35d1_clk_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-clk" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ma35d1_clk_of_match);
+
+static struct platform_driver ma35d1_clk_driver = {
+ .probe = ma35d1_clocks_probe,
+ .driver = {
+ .name = "ma35d1-clk",
+ .of_match_table = ma35d1_clk_of_match,
+ },
+};
+
+static int __init ma35d1_clocks_init(void)
+{
+ return platform_driver_register(&ma35d1_clk_driver);
+}
+
+postcore_initcall(ma35d1_clocks_init);
+
+MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
+MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
new file mode 100644
index 000000000000..faae5a17e425
--- /dev/null
+++ b/drivers/clk/nuvoton/clk-ma35d1.h
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ * Author: Chi-Fang Li <[email protected]>
+ */
+
+#ifndef __DRV_CLK_NUVOTON_MA35D1_H
+#define __DRV_CLK_NUVOTON_MA35D1_H
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/ma35d1-sys.h>
+
+enum ma35d1_pll_type {
+ MA35D1_CAPLL,
+ MA35D1_DDRPLL,
+ MA35D1_APLL,
+ MA35D1_EPLL,
+ MA35D1_VPLL,
+};
+
+enum ma35d1_pll_mode {
+ VSIPLL_INTEGER_MODE,
+ VSIPLL_FRACTIONAL_MODE,
+ VSIPLL_SS_MODE,
+};
+
+/* VSI-PLL CTL0~2 */
+#define VSIPLL_CTL0 0x0
+#define VSIPLL_CTL1 0x4
+#define VSIPLL_CTL2 0x8
+
+/* VSI-PLL Specification limits */
+#define VSIPLL_FREF_MAX_FREQ 200000000UL
+#define VSIPLL_FREF_MIN_FREQ 1000000UL
+#define VSIPLL_FREFDIVM_MAX_FREQ 40000000UL
+#define VSIPLL_FREFDIVM_MIN_FREQ0 1000000UL
+#define VSIPLL_FREFDIVM_MIN_FREQ1 10000000UL
+#define VSIPLL_FCLK_MAX_FREQ 2400000000UL
+#define VSIPLL_FCLK_MIN_FREQ 600000000UL
+#define VSIPLL_FCLKO_MAX_FREQ 2400000000UL
+#define VSIPLL_FCLKO_MIN_FREQ 85700000UL
+#define VSIPLL_SPREAD_RANGE 194
+#define VSIPLL_MODULATION_FREQ 50000
+
+/* Clock Control Registers Offset */
+#define REG_CLK_PWRCTL (0x00)
+#define REG_CLK_SYSCLK0 (0x04)
+#define REG_CLK_SYSCLK1 (0x08)
+#define REG_CLK_APBCLK0 (0x0C)
+#define REG_CLK_APBCLK1 (0x10)
+#define REG_CLK_APBCLK2 (0x14)
+#define REG_CLK_CLKSEL0 (0x18)
+#define REG_CLK_CLKSEL1 (0x1C)
+#define REG_CLK_CLKSEL2 (0x20)
+#define REG_CLK_CLKSEL3 (0x24)
+#define REG_CLK_CLKSEL4 (0x28)
+#define REG_CLK_CLKDIV0 (0x2C)
+#define REG_CLK_CLKDIV1 (0x30)
+#define REG_CLK_CLKDIV2 (0x34)
+#define REG_CLK_CLKDIV3 (0x38)
+#define REG_CLK_CLKDIV4 (0x3C)
+#define REG_CLK_CLKOCTL (0x40)
+#define REG_CLK_STATUS (0x50)
+#define REG_CLK_PLL0CTL0 (0x60)
+#define REG_CLK_PLL2CTL0 (0x80)
+#define REG_CLK_PLL2CTL1 (0x84)
+#define REG_CLK_PLL2CTL2 (0x88)
+#define REG_CLK_PLL3CTL0 (0x90)
+#define REG_CLK_PLL3CTL1 (0x94)
+#define REG_CLK_PLL3CTL2 (0x98)
+#define REG_CLK_PLL4CTL0 (0xA0)
+#define REG_CLK_PLL4CTL1 (0xA4)
+#define REG_CLK_PLL4CTL2 (0xA8)
+#define REG_CLK_PLL5CTL0 (0xB0)
+#define REG_CLK_PLL5CTL1 (0xB4)
+#define REG_CLK_PLL5CTL2 (0xB8)
+#define REG_CLK_CLKDCTL (0xC0)
+#define REG_CLK_CLKDSTS (0xC4)
+#define REG_CLK_CDUPB (0xC8)
+#define REG_CLK_CDLOWB (0xCC)
+#define REG_CLK_CKFLTRCTL (0xD0)
+#define REG_CLK_TESTCLK (0xF0)
+#define REG_CLK_PLLCTL (0x40)
+
+/* Constant Definitions for Clock Controller */
+#define SMICPLLCTL0_FBDIV_POS (0)
+#define SMICPLLCTL0_FBDIV_MSK (0xfful << SMICPLLCTL0_FBDIV_POS)
+#define SMICPLLCTL0_INDIV_POS (8)
+#define SMICPLLCTL0_INDIV_MSK (0xful << SMICPLLCTL0_INDIV_POS)
+#define SMICPLLCTL0_OUTDIV_POS (12)
+#define SMICPLLCTL0_OUTDIV_MSK (0x3ul << SMICPLLCTL0_OUTDIV_POS)
+#define SMICPLLCTL0_PD_POS (16)
+#define SMICPLLCTL0_PD_MSK (0x1ul << SMICPLLCTL0_PD_POS)
+#define SMICPLLCTL0_BP_POS (17)
+#define SMICPLLCTL0_BP_MSK (0x1ul << SMICPLLCTL0_BP_POS)
+#define VSIPLLCTL0_FBDIV_POS (0)
+#define VSIPLLCTL0_FBDIV_MSK (0x7fful << VSIPLLCTL0_FBDIV_POS)
+#define VSIPLLCTL0_INDIV_POS (12)
+#define VSIPLLCTL0_INDIV_MSK (0x3ful << VSIPLLCTL0_INDIV_POS)
+#define VSIPLLCTL0_MODE_POS (18)
+#define VSIPLLCTL0_MODE_MSK (0x3ul << VSIPLLCTL0_MODE_POS)
+#define VSIPLLCTL0_SSRATE_POS (20)
+#define VSIPLLCTL0_SSRATE_MSK (0x7fful << VSIPLLCTL0_SSRATE_POS)
+#define VSIPLLCTL1_PD_POS (0)
+#define VSIPLLCTL1_PD_MSK (0x1ul << VSIPLLCTL1_PD_POS)
+#define VSIPLLCTL1_BP_POS (1)
+#define VSIPLLCTL1_BP_MSK (0x1ul << VSIPLLCTL1_BP_POS)
+#define VSIPLLCTL1_OUTDIV_POS (4)
+#define VSIPLLCTL1_OUTDIV_MSK (0x7ul << VSIPLLCTL1_OUTDIV_POS)
+#define VSIPLLCTL1_FRAC_POS (8)
+#define VSIPLLCTL1_FRAC_MSK (0xfffffful << VSIPLLCTL1_FRAC_POS)
+#define VSIPLLCTL2_SLOPE_POS (0)
+#define VSIPLLCTL2_SLOPE_MSK (0xfffffful << VSIPLLCTL2_SLOPE_POS)
+
+struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type, u8 u8mode,
+ const char *name, const char *parent,
+ unsigned long targetFreq,
+ void __iomem *base,
+ struct regmap *regmap);
+
+struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev,
+ const char *name,
+ const char *parent_name,
+ unsigned long flags,
+ void __iomem *reg, u8 shift,
+ u8 width, u32 mask_bit);
+
+extern spinlock_t ma35d1_lock;
+
+static inline struct clk_hw *ma35d1_clk_fixed(const char *name, int rate)
+{
+ return clk_hw_register_fixed_rate(NULL, name, NULL, 0, rate);
+}
+
+static inline struct clk_hw *ma35d1_clk_mux(const char *name,
+ void __iomem *reg, u8 shift,
+ u8 width,
+ const char *const *parents,
+ int num_parents)
+{
+ return clk_hw_register_mux(NULL, name, parents, num_parents,
+ CLK_SET_RATE_NO_REPARENT, reg, shift,
+ width, 0, &ma35d1_lock);
+}
+
+static inline struct clk_hw *ma35d1_clk_divider(const char *name,
+ const char *parent,
+ void __iomem *reg, u8 shift,
+ u8 width)
+{
+ return clk_hw_register_divider(NULL, name, parent, CLK_SET_RATE_PARENT,
+ reg, shift, width, 0, &ma35d1_lock);
+}
+
+static inline struct clk_hw *ma35d1_clk_divider_pow2(const char *name,
+ const char *parent,
+ void __iomem *reg,
+ u8 shift, u8 width)
+{
+ return clk_hw_register_divider(NULL, name, parent,
+ CLK_DIVIDER_POWER_OF_TWO, reg, shift,
+ width, 0, &ma35d1_lock);
+}
+
+static inline struct clk_hw *ma35d1_clk_divider_table(const char *name,
+ const char *parent,
+ void __iomem *reg,
+ u8 shift, u8 width,
+ const struct clk_div_table *table)
+{
+ return clk_hw_register_divider_table(NULL, name, parent, 0,
+ reg, shift, width, 0, table,
+ &ma35d1_lock);
+}
+
+static inline struct clk_hw *ma35d1_clk_fixed_factor(const char *name,
+ const char *parent,
+ unsigned int mult,
+ unsigned int div)
+{
+ return clk_hw_register_fixed_factor(NULL, name, parent,
+ CLK_SET_RATE_PARENT, mult, div);
+}
+
+static inline struct clk_hw *ma35d1_clk_gate(const char *name,
+ const char *parent,
+ void __iomem *reg, u8 shift)
+{
+ return clk_hw_register_gate(NULL, name, parent, CLK_SET_RATE_PARENT,
+ reg, shift, 0, &ma35d1_lock);
+}
+
+#endif /* __DRV_CLK_NUVOTON_MA35D1_H */
--
2.34.1
From: Jacky Huang <[email protected]>
This adds UART and console driver for Nuvoton ma35d1 Soc.
MA35D1 SoC provides up to 17 UART controllers, each with one uart port.
The ma35d1 uart controller is not compatible with 8250.
The uart controller supports:
- Full-duplex asynchronous communications
- Separates tx and tx 32/32 bytes entry FIFO for data payloads
- Hardware auto-flow control
- Programmable rx buffer trigger level (1/4/8/14/30 bytes)
- Individual programmable baud rate generator for each channel
- Supports nCTS, incoming data, rx FIFO reached threshold and
RS-485 Address Match (AAD mode) wake-up function
- Supports 8-bit rx buffer time-out detection function
- Programmable tx data delay time
- Supports Auto-Baud Rate measurement and baud rate compensation
- Supports break error, frame error, parity error and rx/tx buffer
overflow detection function
– Programmable number of data bit, 5-, 6-, 7-, 8- bit character
– Programmable parity bit, even, odd, no parity or stick parity bit
generation and detection
– Programmable stop bit, 1, 1.5, or 2 stop bit generation
- Supports IrDA SIR function mode
- Supports RS-485 function mode
– Supports RS-485 9-bit mode
– Supports hardware or software enables to program nRTS pin to control
RS-485 transmission direction
- Supports PDMA transfer function
- Support Single-wire function mode.
Signed-off-by: Jacky Huang <[email protected]>
---
drivers/tty/serial/Kconfig | 18 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++++++++++++++++
drivers/tty/serial/ma35d1_serial.h | 93 ++++
include/uapi/linux/serial_core.h | 3 +
5 files changed, 957 insertions(+)
create mode 100644 drivers/tty/serial/ma35d1_serial.c
create mode 100644 drivers/tty/serial/ma35d1_serial.h
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 625358f44419..cb47fe804595 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1562,6 +1562,24 @@ config SERIAL_SUNPLUS_CONSOLE
you can alter that using a kernel command line option such as
"console=ttySUPx".
+config SERIAL_NUVOTON_MA35D1
+ tristate "Nuvoton MA35D1 family UART support"
+ depends on ARCH_NUVOTON || COMPILE_TEST
+ select SERIAL_CORE
+ help
+ This driver supports Nuvoton MA35D1 family UART ports. If you would
+ like to use them, you must answer Y or M to this option. Note that
+ for use as console, it must be included in kernel and not as a
+ module
+
+config SERIAL_NUVOTON_MA35D1_CONSOLE
+ bool "Console on a Nuvotn MA35D1 family UART port"
+ depends on SERIAL_NUVOTON_MA35D1=y
+ select SERIAL_CORE_CONSOLE
+ help
+ Select this options if you'd like to use the UART port0 of the
+ Nuvoton MA35D1 family as a console.
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index cd9afd9e3018..71ebeba06ff2 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -93,3 +93,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
+obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c
new file mode 100644
index 000000000000..8940d07c3777
--- /dev/null
+++ b/drivers/tty/serial/ma35d1_serial.c
@@ -0,0 +1,842 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MA35D1 serial driver
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/sysrq.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/clk.h>
+#include <linux/serial_reg.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/nmi.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <asm/serial.h>
+#include "ma35d1_serial.h"
+
+#define UART_NR 17
+
+static struct uart_driver ma35d1serial_reg;
+struct clk *clk;
+
+struct uart_ma35d1_port {
+ struct uart_port port;
+ u16 capabilities; /* port capabilities */
+ u8 ier;
+ u8 lcr;
+ u8 mcr;
+ u8 mcr_mask; /* mask of user bits */
+ u8 mcr_force; /* mask of forced bits */
+ struct serial_rs485 rs485; /* rs485 settings */
+ u32 baud_rate;
+ int rx_count;
+ u32 console_baud_rate;
+ u32 console_line;
+ u32 console_int;
+};
+
+static struct device_node *ma35d1serial_uart_nodes[UART_NR];
+static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
+static void __stop_tx(struct uart_ma35d1_port *p);
+static void transmit_chars(struct uart_ma35d1_port *up);
+
+static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port *uart)
+{
+ return container_of(uart, struct uart_ma35d1_port, port);
+}
+
+static u32 serial_in(struct uart_ma35d1_port *p, int offset)
+{
+ return __raw_readl(p->port.membase + offset);
+}
+
+static void serial_out(struct uart_ma35d1_port *p, int offset, int value)
+{
+ __raw_writel(value, p->port.membase + offset);
+}
+
+static void __stop_tx(struct uart_ma35d1_port *p)
+{
+ u32 ier;
+
+ ier = serial_in(p, UART_REG_IER);
+ if (ier & THRE_IEN)
+ serial_out(p, UART_REG_IER, ier & ~THRE_IEN);
+}
+
+static void ma35d1serial_stop_tx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+
+ __stop_tx(up);
+}
+
+static void ma35d1serial_start_tx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ u32 ier;
+ struct circ_buf *xmit = &up->port.state->xmit;
+
+ ier = serial_in(up, UART_REG_IER);
+ serial_out(up, UART_REG_IER, ier & ~THRE_IEN);
+ if (uart_circ_chars_pending(xmit) <
+ (16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0x3F)))
+ transmit_chars(up);
+ serial_out(up, UART_REG_IER, ier | THRE_IEN);
+}
+
+static void ma35d1serial_stop_rx(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+
+ serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) & ~RDA_IEN);
+}
+
+static void
+receive_chars(struct uart_ma35d1_port *up)
+{
+ u8 ch;
+ u32 fsr;
+ u32 isr;
+ u32 dcnt;
+ char flag;
+
+ isr = serial_in(up, UART_REG_ISR);
+ fsr = serial_in(up, UART_REG_FSR);
+
+ while (!(fsr & RX_EMPTY)) {
+ flag = TTY_NORMAL;
+ up->port.icount.rx++;
+
+ if (unlikely(fsr & (BIF | FEF | PEF | RX_OVER_IF))) {
+ if (fsr & BIF) {
+ serial_out(up, UART_REG_FSR, BIF);
+ up->port.icount.brk++;
+ if (uart_handle_break(&up->port))
+ continue;
+ }
+ if (fsr & FEF) {
+ serial_out(up, UART_REG_FSR, FEF);
+ up->port.icount.frame++;
+ }
+ if (fsr & PEF) {
+ serial_out(up, UART_REG_FSR, PEF);
+ up->port.icount.parity++;
+ }
+ if (fsr & RX_OVER_IF) {
+ serial_out(up, UART_REG_FSR, RX_OVER_IF);
+ up->port.icount.overrun++;
+ }
+ if (fsr & BIF)
+ flag = TTY_BREAK;
+ if (fsr & PEF)
+ flag = TTY_PARITY;
+ if (fsr & FEF)
+ flag = TTY_FRAME;
+ }
+ ch = (u8)serial_in(up, UART_REG_RBR);
+ if (uart_handle_sysrq_char(&up->port, ch))
+ continue;
+
+ uart_insert_char(&up->port, fsr, RX_OVER_IF, ch, flag);
+ up->rx_count++;
+ dcnt = (serial_in(up, UART_REG_FSR) >> 8) & 0x3f;
+ if (up->rx_count > 1023) {
+ spin_lock(&up->port.lock);
+ tty_flip_buffer_push(&up->port.state->port);
+ spin_unlock(&up->port.lock);
+ up->rx_count = 0;
+ if ((isr & RXTO_IF) && (dcnt == 0))
+ goto tout_end;
+ }
+ if (isr & RDA_IF) {
+ if (dcnt == 1)
+ return;
+ }
+ fsr = serial_in(up, UART_REG_FSR);
+ }
+ spin_lock(&up->port.lock);
+ tty_flip_buffer_push(&up->port.state->port);
+ spin_unlock(&up->port.lock);
+tout_end:
+ up->rx_count = 0;
+}
+
+static void transmit_chars(struct uart_ma35d1_port *up)
+{
+ struct circ_buf *xmit = &up->port.state->xmit;
+ int count = 16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0xF);
+
+ if (serial_in(up, UART_REG_FSR) & TX_FULL)
+ count = 0;
+ if (up->port.x_char) {
+ serial_out(up, UART_REG_THR, up->port.x_char);
+ up->port.icount.tx++;
+ up->port.x_char = 0;
+ return;
+ }
+ if (uart_tx_stopped(&up->port)) {
+ ma35d1serial_stop_tx(&up->port);
+ return;
+ }
+ if (uart_circ_empty(xmit)) {
+ __stop_tx(up);
+ return;
+ }
+ while (count > 0) {
+ serial_out(up, UART_REG_THR, xmit->buf[xmit->tail]);
+ xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+ up->port.icount.tx++;
+ count--;
+ if (uart_circ_empty(xmit))
+ break;
+ }
+ if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
+ uart_write_wakeup(&up->port);
+ if (uart_circ_empty(xmit))
+ __stop_tx(up);
+}
+
+static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)dev_id;
+ u32 isr, fsr;
+
+ isr = serial_in(up, UART_REG_ISR);
+ fsr = serial_in(up, UART_REG_FSR);
+ if (isr & (RDA_IF | RXTO_IF))
+ receive_chars(up);
+ if (isr & THRE_INT)
+ transmit_chars(up);
+ if (fsr & (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF))
+ serial_out(up, UART_REG_FSR,
+ (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF));
+
+ return IRQ_HANDLED;
+}
+
+static u32 ma35d1serial_tx_empty(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ u32 fsr;
+
+ fsr = serial_in(up, UART_REG_FSR);
+ return (fsr & (TE_FLAG | TX_EMPTY)) == (TE_FLAG | TX_EMPTY) ?
+ TIOCSER_TEMT : 0;
+}
+
+static u32 ma35d1serial_get_mctrl(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ u32 status;
+ u32 ret = 0;
+
+ status = serial_in(up, UART_REG_MSR);
+ if (!(status & 0x10))
+ ret |= TIOCM_CTS;
+ return ret;
+}
+
+static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ u32 mcr = 0;
+ u32 ier = 0;
+
+ if (mctrl & TIOCM_RTS) {
+ /* set RTS high level trigger */
+ mcr = serial_in(up, UART_REG_MCR);
+ mcr |= 0x200;
+ mcr &= ~(0x2);
+ }
+ if (up->mcr & UART_MCR_AFE) {
+ /* set RTS high level trigger */
+ mcr = serial_in(up, UART_REG_MCR);
+ mcr |= 0x200;
+ mcr &= ~(0x2);
+
+ /* enable CTS/RTS auto-flow control */
+ serial_out(up, UART_REG_IER,
+ (serial_in(up, UART_REG_IER) | (0x3000)));
+
+ /* Set hardware flow control */
+ up->port.flags |= UPF_HARD_FLOW;
+ } else {
+ /* disable CTS/RTS auto-flow control */
+ ier = serial_in(up, UART_REG_IER);
+ ier &= ~(0x3000);
+ serial_out(up, UART_REG_IER, ier);
+
+ /* un-set hardware flow control */
+ up->port.flags &= ~UPF_HARD_FLOW;
+ }
+
+ /* set CTS high level trigger */
+ serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) | (0x100)));
+ serial_out(up, UART_REG_MCR, mcr);
+}
+
+static void ma35d1serial_break_ctl(struct uart_port *port, int break_state)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ unsigned long flags;
+ u32 lcr;
+
+ spin_lock_irqsave(&up->port.lock, flags);
+ lcr = serial_in(up, UART_REG_LCR);
+ if (break_state != 0)
+ lcr |= BCB; /* set break */
+ else
+ lcr &= ~BCB; /* clr break */
+ serial_out(up, UART_REG_LCR, lcr);
+ spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static int ma35d1serial_startup(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ struct tty_struct *tty = port->state->port.tty;
+ int retval;
+
+ /* Reset FIFO */
+ serial_out(up, UART_REG_FCR, TFR | RFR /* | RX_DIS */);
+
+ /* Clear pending interrupts */
+ serial_out(up, UART_REG_ISR, 0xFFFFFFFF);
+
+ retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
+ tty ? tty->name : "ma35d1_serial", port);
+ if (retval) {
+ dev_err(up->port.dev, "request irq failed.\n");
+ return retval;
+ }
+
+ /* Now, initialize the UART */
+ /* FIFO trigger level 4 byte */
+ /* RTS trigger level 8 bytes */
+ serial_out(up, UART_REG_FCR,
+ serial_in(up, UART_REG_FCR) | 0x10 | 0x20000);
+ serial_out(up, UART_REG_LCR, 0x7); /* 8 bit */
+ serial_out(up, UART_REG_TOR, 0x40);
+ serial_out(up, UART_REG_IER,
+ RTO_IEN | RDA_IEN | TIME_OUT_EN | BUFERR_IEN);
+ return 0;
+}
+
+static void ma35d1serial_shutdown(struct uart_port *port)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+
+ free_irq(port->irq, port);
+
+ /* Disable interrupts from this port */
+ serial_out(up, UART_REG_IER, 0);
+}
+
+static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
+{
+ u32 quot;
+
+ quot = (port->uartclk / baud) - 2;
+ return quot;
+}
+
+static void ma35d1serial_set_termios(struct uart_port *port,
+ struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+ u32 lcr = 0;
+ unsigned long flags;
+ u32 baud, quot;
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ lcr = 0;
+ break;
+ case CS6:
+ lcr |= 1;
+ break;
+ case CS7:
+ lcr |= 2;
+ break;
+ case CS8:
+ default:
+ lcr |= 3;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ lcr |= NSB;
+ if (termios->c_cflag & PARENB)
+ lcr |= PBE;
+ if (!(termios->c_cflag & PARODD))
+ lcr |= EPE;
+ if (termios->c_cflag & CMSPAR)
+ lcr |= SPE;
+
+ baud = uart_get_baud_rate(port, termios, old, port->uartclk / 0xffff,
+ port->uartclk / 11);
+
+ quot = ma35d1serial_get_divisor(port, baud);
+
+ /*
+ * Ok, we're now changing the port state. Do it with
+ * interrupts disabled.
+ */
+ spin_lock_irqsave(&up->port.lock, flags);
+
+ up->port.read_status_mask = RX_OVER_IF;
+ if (termios->c_iflag & INPCK)
+ up->port.read_status_mask |= FEF | PEF;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ up->port.read_status_mask |= BIF;
+
+ /*
+ * Characteres to ignore
+ */
+ up->port.ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ up->port.ignore_status_mask |= FEF | PEF;
+ if (termios->c_iflag & IGNBRK) {
+ up->port.ignore_status_mask |= BIF;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ up->port.ignore_status_mask |= RX_OVER_IF;
+ }
+ if (termios->c_cflag & CRTSCTS)
+ up->mcr |= UART_MCR_AFE;
+ else
+ up->mcr &= ~UART_MCR_AFE;
+
+ ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
+ serial_out(up, UART_REG_BAUD, quot | 0x30000000);
+ serial_out(up, UART_REG_LCR, lcr);
+ spin_unlock_irqrestore(&up->port.lock, flags);
+}
+
+static void ma35d1serial_release_port(struct uart_port *port)
+{
+ iounmap(port->membase);
+ port->membase = NULL;
+}
+
+static int ma35d1serial_request_port(struct uart_port *port)
+{
+ return 0;
+}
+
+static void ma35d1serial_config_port(struct uart_port *port, int flags)
+{
+ int ret;
+
+ /*
+ * Find the region that we can probe for. This in turn
+ * tells us whether we can probe for the type of port.
+ */
+ ret = ma35d1serial_request_port(port);
+ if (ret < 0)
+ return;
+ port->type = PORT_MA35D1;
+}
+
+static int ma35d1serial_verify_port(struct uart_port *port,
+ struct serial_struct *ser)
+{
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_MA35D1)
+ return -EINVAL;
+ return 0;
+}
+
+static const char *ma35d1serial_type(struct uart_port *port)
+{
+ return (port->type == PORT_MA35D1) ? "MA35D1" : NULL;
+}
+
+/* Enable or disable the rs485 support */
+static int ma35d1serial_config_rs485(struct uart_port *port,
+ struct ktermios *termios,
+ struct serial_rs485 *rs485conf)
+{
+ struct uart_ma35d1_port *p = to_ma35d1_uart_port(port);
+
+ p->rs485 = *rs485conf;
+
+ if (p->rs485.delay_rts_before_send >= 1000)
+ p->rs485.delay_rts_before_send = 1000;
+
+ serial_out(p, UART_FUN_SEL,
+ (serial_in(p, UART_FUN_SEL) & ~FUN_SEL_MASK));
+
+ if (rs485conf->flags & SER_RS485_ENABLED) {
+ serial_out(p, UART_FUN_SEL,
+ (serial_in(p, UART_FUN_SEL) | FUN_SEL_RS485));
+
+ if (rs485conf->flags & SER_RS485_RTS_ON_SEND)
+ serial_out(p, UART_REG_MCR,
+ (serial_in(p, UART_REG_MCR) & ~0x200));
+ else
+ serial_out(p, UART_REG_MCR,
+ (serial_in(p, UART_REG_MCR) | 0x200));
+
+ /* set auto direction mode */
+ serial_out(p, UART_REG_ALT_CSR,
+ (serial_in(p, UART_REG_ALT_CSR) | (1 << 10)));
+ }
+ return 0;
+}
+
+static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
+{
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static const struct uart_ops ma35d1serial_ops = {
+ .tx_empty = ma35d1serial_tx_empty,
+ .set_mctrl = ma35d1serial_set_mctrl,
+ .get_mctrl = ma35d1serial_get_mctrl,
+ .stop_tx = ma35d1serial_stop_tx,
+ .start_tx = ma35d1serial_start_tx,
+ .stop_rx = ma35d1serial_stop_rx,
+ .break_ctl = ma35d1serial_break_ctl,
+ .startup = ma35d1serial_startup,
+ .shutdown = ma35d1serial_shutdown,
+ .set_termios = ma35d1serial_set_termios,
+ .type = ma35d1serial_type,
+ .release_port = ma35d1serial_release_port,
+ .request_port = ma35d1serial_request_port,
+ .config_port = ma35d1serial_config_port,
+ .verify_port = ma35d1serial_verify_port,
+ .ioctl = ma35d1serial_ioctl,
+};
+
+static const struct of_device_id ma35d1_serial_of_match[] = {
+ { .compatible = "nuvoton,ma35d1-uart" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ma35d1_serial_of_match);
+
+#ifdef CONFIG_SERIAL_NUVOTON_MA35D1_CONSOLE
+
+static void ma35d1serial_console_putchar(struct uart_port *port,
+ unsigned char ch)
+{
+ struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
+
+ do {
+ } while ((serial_in(up, UART_REG_FSR) & TX_FULL));
+ serial_out(up, UART_REG_THR, ch);
+}
+
+/*
+ * Print a string to the serial port trying not to disturb
+ * any possible real use of the port...
+ *
+ * The console_lock must be held when we get here.
+ */
+static void ma35d1serial_console_write(struct console *co,
+ const char *s, u32 count)
+{
+ struct uart_ma35d1_port *up = &ma35d1serial_ports[co->index];
+ unsigned long flags;
+ u32 ier;
+
+ local_irq_save(flags);
+
+ /*
+ * First save the IER then disable the interrupts
+ */
+ ier = serial_in(up, UART_REG_IER);
+ serial_out(up, UART_REG_IER, 0);
+
+ uart_console_write(&up->port, s, count, ma35d1serial_console_putchar);
+
+ /*
+ * Finally, wait for transmitter to become empty
+ * and restore the IER
+ */
+ do {
+ } while (!(serial_in(up, UART_REG_FSR) & TX_EMPTY));
+ serial_out(up, UART_REG_IER, ier);
+ local_irq_restore(flags);
+}
+
+static int __init ma35d1serial_console_setup(struct console *co,
+ char *options)
+{
+ struct device_node *np = ma35d1serial_uart_nodes[co->index];
+ struct uart_ma35d1_port *p = &ma35d1serial_ports[co->index];
+ u32 val32[4];
+ struct uart_port *port;
+ int baud = 115200;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+
+ /*
+ * Check whether an invalid uart number has been specified, and
+ * if so, search for the first available port that does have
+ * console support.
+ */
+ if ((co->index < 0) || (co->index >= UART_NR)) {
+ pr_debug("Console Port%x out of range\n", co->index);
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32_array(np, "reg", val32, 4) != 0)
+ return -EINVAL;
+ p->port.iobase = val32[1];
+ p->port.membase = ioremap(p->port.iobase, 0x10000);
+ p->port.ops = &ma35d1serial_ops;
+ p->port.line = 0;
+ p->port.uartclk = 24000000;
+
+ port = &ma35d1serial_ports[co->index].port;
+ return uart_set_options(port, co, baud, parity, bits, flow);
+}
+
+static struct console ma35d1serial_console = {
+ .name = "ttyS",
+ .write = ma35d1serial_console_write,
+ .device = uart_console_device,
+ .setup = ma35d1serial_console_setup,
+ .flags = CON_PRINTBUFFER | CON_ENABLED,
+ .index = -1,
+ .data = &ma35d1serial_reg,
+};
+
+static void
+ma35d1serial_console_init_port(void)
+{
+ int i = 0;
+ struct device_node *np;
+
+ for_each_matching_node(np, ma35d1_serial_of_match) {
+ if (ma35d1serial_uart_nodes[i] == NULL) {
+ ma35d1serial_uart_nodes[i] = np;
+ i++;
+ }
+ }
+}
+
+static int __init ma35d1serial_console_init(void)
+{
+ ma35d1serial_console_init_port();
+ register_console(&ma35d1serial_console);
+ return 0;
+}
+console_initcall(ma35d1serial_console_init);
+
+#define MA35D1SERIAL_CONSOLE (&ma35d1serial_console)
+#else
+#define MA35D1SERIAL_CONSOLE NULL
+#endif
+
+static struct uart_driver ma35d1serial_reg = {
+ .owner = THIS_MODULE,
+ .driver_name = "serial",
+ .dev_name = "ttyS",
+ .major = TTY_MAJOR,
+ .minor = 64,
+ .cons = MA35D1SERIAL_CONSOLE,
+ .nr = UART_NR,
+};
+
+/**
+ * Suspend one serial port.
+ */
+void ma35d1serial_suspend_port(int line)
+{
+ uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
+}
+EXPORT_SYMBOL(ma35d1serial_suspend_port);
+
+/**
+ * Resume one serial port.
+ */
+void ma35d1serial_resume_port(int line)
+{
+ struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
+
+ uart_resume_port(&ma35d1serial_reg, &up->port);
+}
+EXPORT_SYMBOL(ma35d1serial_resume_port);
+
+/*
+ * Register a set of serial devices attached to a platform device.
+ * The list is terminated with a zero flags entry, which means we expect
+ * all entries to have at least UPF_BOOT_AUTOCONF set.
+ */
+static int ma35d1serial_probe(struct platform_device *pdev)
+{
+ struct resource *res_mem;
+ struct uart_ma35d1_port *up;
+ int ret;
+ struct clk *clk;
+ int err;
+
+ if (pdev->dev.of_node) {
+ ret = of_alias_get_id(pdev->dev.of_node, "serial");
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to get alias/pdev id, errno %d\n",
+ ret);
+ return ret;
+ }
+ }
+ up = &ma35d1serial_ports[ret];
+ up->port.line = ret;
+ res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_mem)
+ return -ENODEV;
+
+ up->port.iobase = res_mem->start;
+ up->port.membase = ioremap(up->port.iobase, 0x10000);
+ up->port.ops = &ma35d1serial_ops;
+
+ spin_lock_init(&up->port.lock);
+
+ clk = of_clk_get(pdev->dev.of_node, 0);
+ if (IS_ERR(clk)) {
+ err = PTR_ERR(clk);
+ dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
+ return -ENOENT;
+ }
+ err = clk_prepare_enable(clk);
+ if (err)
+ return -ENOENT;
+
+ if (up->port.line != 0)
+ up->port.uartclk = clk_get_rate(clk);
+ up->port.irq = platform_get_irq(pdev, 0);
+ up->port.dev = &pdev->dev;
+ up->port.flags = UPF_BOOT_AUTOCONF;
+ up->port.rs485_config = ma35d1serial_config_rs485;
+ ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
+ platform_set_drvdata(pdev, up);
+ return 0;
+}
+
+/*
+ * Remove serial ports registered against a platform device.
+ */
+static int ma35d1serial_remove(struct platform_device *dev)
+{
+ int i;
+ struct uart_port *port = platform_get_drvdata(dev);
+
+ free_irq(port->irq, port);
+ for (i = 0; i < UART_NR; i++) {
+ struct uart_ma35d1_port *up = &ma35d1serial_ports[i];
+
+ if (up->port.dev == &dev->dev)
+ uart_remove_one_port(&ma35d1serial_reg, &up->port);
+ }
+ return 0;
+}
+
+static int ma35d1serial_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ int i;
+ struct uart_ma35d1_port *up;
+
+ if (dev->dev.of_node)
+ i = of_alias_get_id(dev->dev.of_node, "serial");
+ if (i < 0) {
+ dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
+ i);
+ return i;
+ }
+ up = &ma35d1serial_ports[i];
+ if (i == 0) {
+ up->console_baud_rate = serial_in(up, UART_REG_BAUD);
+ up->console_line = serial_in(up, UART_REG_LCR);
+ up->console_int = serial_in(up, UART_REG_IER);
+ }
+ return 0;
+}
+
+static int ma35d1serial_resume(struct platform_device *dev)
+{
+ int i;
+ struct uart_ma35d1_port *up;
+
+ if (dev->dev.of_node)
+ i = of_alias_get_id(dev->dev.of_node, "serial");
+ if (i < 0) {
+ dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
+ i);
+ return i;
+ }
+ up = &ma35d1serial_ports[i];
+ if (i == 0) {
+ serial_out(up, UART_REG_BAUD, up->console_baud_rate);
+ serial_out(up, UART_REG_LCR, up->console_line);
+ serial_out(up, UART_REG_IER, up->console_int);
+ }
+ return 0;
+}
+
+static struct platform_driver ma35d1serial_driver = {
+ .probe = ma35d1serial_probe,
+ .remove = ma35d1serial_remove,
+ .suspend = ma35d1serial_suspend,
+ .resume = ma35d1serial_resume,
+ .driver = {
+ .name = "ma35d1-uart",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(ma35d1_serial_of_match),
+ },
+};
+
+static int __init ma35d1serial_init(void)
+{
+ int ret;
+
+ ret = uart_register_driver(&ma35d1serial_reg);
+ if (ret)
+ return ret;
+ ret = platform_driver_register(&ma35d1serial_driver);
+ if (ret)
+ uart_unregister_driver(&ma35d1serial_reg);
+ return ret;
+}
+
+static void __exit ma35d1serial_exit(void)
+{
+ platform_driver_unregister(&ma35d1serial_driver);
+ uart_unregister_driver(&ma35d1serial_reg);
+}
+
+module_init(ma35d1serial_init);
+module_exit(ma35d1serial_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MA35D1 serial driver");
+MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR);
+
diff --git a/drivers/tty/serial/ma35d1_serial.h b/drivers/tty/serial/ma35d1_serial.h
new file mode 100644
index 000000000000..5fd845c31b29
--- /dev/null
+++ b/drivers/tty/serial/ma35d1_serial.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MA35D1 serial driver header file
+ * Copyright (C) 2023 Nuvoton Technology Corp.
+ */
+#ifndef __MA35D1_SERIAL_H__
+#define __MA35D1_SERIAL_H__
+
+/* UART Receive/Transmit Buffer Register */
+#define UART_REG_RBR 0x00
+#define UART_REG_THR 0x00
+
+/* UART Interrupt Enable Register */
+#define UART_REG_IER 0x04
+#define RDA_IEN 0x00000001 /* RBR Available Interrupt Enable */
+#define THRE_IEN 0x00000002 /* THR Empty Interrupt Enable */
+#define RLS_IEN 0x00000004 /* RX Line Status Interrupt Enable */
+#define RTO_IEN 0x00000010 /* RX Time-out Interrupt Enable */
+#define BUFERR_IEN 0x00000020 /* Buffer Error Interrupt Enable */
+#define TIME_OUT_EN 0x00000800 /* RX Buffer Time-out Counter Enable */
+
+/* UART FIFO Control Register */
+#define UART_REG_FCR 0x08
+#define RFR 0x00000002 /* RX Field Software Reset */
+#define TFR 0x00000004 /* TX Field Software Reset */
+
+/* UART Line Control Register */
+#define UART_REG_LCR 0x0C
+#define NSB 0x00000004 /* Number of “STOP Bit” */
+#define PBE 0x00000008 /* Parity Bit Enable */
+#define EPE 0x00000010 /* Even Parity Enable */
+#define SPE 0x00000020 /* Stick Parity Enable */
+#define BCB 0x00000040 /* Break Control */
+
+/* UART Modem Control Register */
+#define UART_REG_MCR 0x10
+#define RTS 0x00000020 /* nRTS Signal Control */
+#define RTSACTLV 0x00000200 /* nRTS Pin Active Level */
+#define RTSSTS 0x00002000 /* nRTS Pin Status (Read Only) */
+
+/* UART Modem Status Register */
+#define UART_REG_MSR 0x14
+#define CTSDETF 0x00000001 /* Detect nCTS State Change Flag */
+#define CTSSTS 0x00000010 /* nCTS Pin Status (Read Only) */
+#define CTSACTLV 0x00000100 /* nCTS Pin Active Level */
+
+/* UART FIFO Status Register */
+#define UART_REG_FSR 0x18
+#define RX_OVER_IF 0x00000001 /* RX Overflow Error Interrupt Flag */
+#define PEF 0x00000010 /* Parity Error Flag*/
+#define FEF 0x00000020 /* Framing Error Flag */
+#define BIF 0x00000040 /* Break Interrupt Flag */
+#define RX_EMPTY 0x00004000 /* Receiver FIFO Empty (Read Only) */
+#define RX_FULL 0x00008000 /* Receiver FIFO Full (Read Only) */
+#define TX_EMPTY 0x00400000 /* Transmitter FIFO Empty (Read Only) */
+#define TX_FULL 0x00800000 /* Transmitter FIFO Full (Read Only) */
+#define TX_OVER_IF 0x01000000 /* TX Overflow Error Interrupt Flag */
+#define TE_FLAG 0x10000000 /* Transmitter Empty Flag (Read Only) */
+
+/* UART Interrupt Status Register */
+#define UART_REG_ISR 0x1C
+#define RDA_IF 0x00000001 /* RBR Available Interrupt Flag */
+#define THRE_IF 0x00000002 /* THR Empty Interrupt Flag */
+#define RLSIF 0x00000004 /* Receive Line Interrupt Flag */
+#define MODEMIF 0x00000008 /* MODEM Interrupt Flag */
+#define RXTO_IF 0x00000010 /* RX Time-out Interrupt Flag */
+#define BUFEIF 0x00000020 /* Buffer Error Interrupt Flag */
+#define WK_IF 0x00000040 /* UART Wake-up Interrupt Flag */
+#define RDAINT 0x00000100 /* RBR Available Interrupt Indicator */
+#define THRE_INT 0x00000200 /* THR Empty Interrupt Indicator */
+
+/* UART Time-out Register */
+#define UART_REG_TOR 0x20
+
+/* UART Baud Rate Divider Register */
+#define UART_REG_BAUD 0x24
+
+/* UART Alternate Control/Status Register */
+#define UART_REG_ALT_CSR 0x2C
+
+/* UART Function Select Register */
+#define UART_FUN_SEL 0x30
+#define FUN_SEL_UART 0x00000000
+#define FUN_SEL_RS485 0x00000003
+#define FUN_SEL_MASK 0x00000007
+
+/* UART Wake-up Control Register */
+#define UART_REG_WKCTL 0x40
+
+/* UART Wake-up Status Register */
+#define UART_REG_WKSTS 0x44
+
+#endif /* __MA35D1_SERIAL_H__ */
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 281fa286555c..c6d53db17042 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -279,4 +279,7 @@
/* Sunplus UART */
#define PORT_SUNPLUS 123
+/* Nuvoton MA35D1 UART */
+#define PORT_MA35D1 124
+
#endif /* _UAPILINUX_SERIAL_CORE_H */
--
2.34.1
From: Jacky Huang <[email protected]>
Add entry for Nuvton ma35d1 maintainer and files
Signed-off-by: Jacky Huang <[email protected]>
---
MAINTAINERS | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index ec57c42ed544..b42d5c052863 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2535,6 +2535,18 @@ F: arch/arm/mach-npcm/wpcm450.c
F: drivers/*/*/*wpcm*
F: drivers/*/*wpcm*
+ARM/NUVOTON MA35 ARCHITECTURE
+M: Jacky Huang <[email protected]>
+M: Shan-Chun Hung <[email protected]>
+L: [email protected] (moderated for non-subscribers)
+S: Supported
+F: Documentation/devicetree/bindings/*/*nuvoton*
+F: arch/arm64/boot/dts/nuvoton/
+F: drivers/*/*/*ma35d1*
+F: drivers/*/*ma35d1*
+F: include/dt-bindings/*/*ma35d1*
+F: include/linux/mfd/ma35d1-sys.h
+
ARM/NXP S32G ARCHITECTURE
M: Chester Lin <[email protected]>
R: Andreas Färber <[email protected]>
--
2.34.1
On Wed, Mar 15, 2023 at 07:29:01AM +0000, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This adds UART and console driver for Nuvoton ma35d1 Soc.
>
> MA35D1 SoC provides up to 17 UART controllers, each with one uart port.
> The ma35d1 uart controller is not compatible with 8250.
A new UART being designed that is not an 8250 compatible? Why????
Anyway, some minor comments:
> drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++++++++++++++++
> drivers/tty/serial/ma35d1_serial.h | 93 ++++
Why do you have a .h file for only a single .c file? Please just put
them both together into one .c file.
> include/uapi/linux/serial_core.h | 3 +
Why do you need this #define? Are you using it in userspace now? If
not, why have it?
> +static void
> +receive_chars(struct uart_ma35d1_port *up)
Please just put all one one line.
> +{
> + u8 ch;
> + u32 fsr;
> + u32 isr;
> + u32 dcnt;
> + char flag;
> +
> + isr = serial_in(up, UART_REG_ISR);
> + fsr = serial_in(up, UART_REG_FSR);
> +
> + while (!(fsr & RX_EMPTY)) {
You have no way out if the hardware is stuck? That feels wrong.
> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
> +{
> + switch (cmd) {
> + default:
> + return -ENOIOCTLCMD;
> + }
> + return 0;
> +}
You do not need to handle any ioctls?
> +static void ma35d1serial_console_putchar(struct uart_port *port,
> + unsigned char ch)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + do {
> + } while ((serial_in(up, UART_REG_FSR) & TX_FULL));
Again, no way out if this gets stuck in a loop?
> +/**
> + * Suspend one serial port.
> + */
> +void ma35d1serial_suspend_port(int line)
> +{
> + uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
> +}
> +EXPORT_SYMBOL(ma35d1serial_suspend_port);
Why is this exported? Who uses it?
And why not EXPORT_SYMBOL_GPL()?
> +
> +/**
> + * Resume one serial port.
> + */
> +void ma35d1serial_resume_port(int line)
> +{
> + struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
> +
> + uart_resume_port(&ma35d1serial_reg, &up->port);
> +}
> +EXPORT_SYMBOL(ma35d1serial_resume_port);
Same here, who calls this and why?
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -279,4 +279,7 @@
> /* Sunplus UART */
> #define PORT_SUNPLUS 123
>
> +/* Nuvoton MA35D1 UART */
> +#define PORT_MA35D1 124
Again, why is this define needed?
thanks,
greg k-h
Dear Greg,
Thank you for your review.
On 2023/3/15 下午 03:37, Greg KH wrote:
> On Wed, Mar 15, 2023 at 07:29:01AM +0000, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> This adds UART and console driver for Nuvoton ma35d1 Soc.
>>
>> MA35D1 SoC provides up to 17 UART controllers, each with one uart port.
>> The ma35d1 uart controller is not compatible with 8250.
> A new UART being designed that is not an 8250 compatible? Why????
>
> Anyway, some minor comments:
This UART controller was designed for over 15 years ago and was used on
many Nuvoton chips.
The register interface is not compatible with 8250, so the 8250 driver
cannot be applied, but
the functions are compatible.
>> drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++++++++++++++++
>> drivers/tty/serial/ma35d1_serial.h | 93 ++++
> Why do you have a .h file for only a single .c file? Please just put
> them both together into one .c file.
OK, we will put the .h into .c in the next version.
>> include/uapi/linux/serial_core.h | 3 +
> Why do you need this #define? Are you using it in userspace now? If
> not, why have it?
Actually, we do not use it from userspace. I will remove it in the next
version.
>> +static void
>> +receive_chars(struct uart_ma35d1_port *up)
> Please just put all one one line.
>
OK, I will fix it.
>> +{
>> + u8 ch;
>> + u32 fsr;
>> + u32 isr;
>> + u32 dcnt;
>> + char flag;
>> +
>> + isr = serial_in(up, UART_REG_ISR);
>> + fsr = serial_in(up, UART_REG_FSR);
>> +
>> + while (!(fsr & RX_EMPTY)) {
> You have no way out if the hardware is stuck? That feels wrong.
Thanks for pointing this out. I will add a timeout check to this
infinite loop.
>> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
>> +{
>> + switch (cmd) {
>> + default:
>> + return -ENOIOCTLCMD;
>> + }
>> + return 0;
>> +}
> You do not need to handle any ioctls?
Yes, we do not handle ioctls.
I will remove both ma35d1serial_ioctl() and "ioctl =
ma35d1serial_ioctl," in the next version.
>> +static void ma35d1serial_console_putchar(struct uart_port *port,
>> + unsigned char ch)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + do {
>> + } while ((serial_in(up, UART_REG_FSR) & TX_FULL));
> Again, no way out if this gets stuck in a loop?
OK, we will fix it in the next version.
>> +/**
>> + * Suspend one serial port.
>> + */
>> +void ma35d1serial_suspend_port(int line)
>> +{
>> + uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
>> +}
>> +EXPORT_SYMBOL(ma35d1serial_suspend_port);
> Why is this exported? Who uses it?
>
> And why not EXPORT_SYMBOL_GPL()?
>
>> +
>> +/**
>> + * Resume one serial port.
>> + */
>> +void ma35d1serial_resume_port(int line)
>> +{
>> + struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
>> +
>> + uart_resume_port(&ma35d1serial_reg, &up->port);
>> +}
>> +EXPORT_SYMBOL(ma35d1serial_resume_port);
> Same here, who calls this and why?
The ma35d1serial_suspend_port() and ma35d1serial_resume_port() were used in
previous ARM9 projects for userspace proprietary suspend/resume control.
As it's obsoleted in ma35s1, I will remove these two functions in the
next version.
>> --- a/include/uapi/linux/serial_core.h
>> +++ b/include/uapi/linux/serial_core.h
>> @@ -279,4 +279,7 @@
>> /* Sunplus UART */
>> #define PORT_SUNPLUS 123
>>
>> +/* Nuvoton MA35D1 UART */
>> +#define PORT_MA35D1 124
> Again, why is this define needed?
As replied above, we will remove the serial_core.h modification from
this patch.
> thanks,
>
> greg k-h
Best Regards,
Jacky Huang
Hi Jacky,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on robh/for-next]
[also build test WARNING on clk/clk-next tty/tty-testing tty/tty-next tty/tty-linus linus/master v6.3-rc2 next-20230315]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Jacky-Huang/arm64-Kconfig-platforms-Add-config-for-Nuvoton-MA35-platform/20230315-153355
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20230315072902.9298-15-ychuang570808%40gmail.com
patch subject: [PATCH 14/15] tty: serial: Add Nuvoton ma35d1 serial driver support
config: sparc-allyesconfig (https://download.01.org/0day-ci/archive/20230315/[email protected]/config)
compiler: sparc64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/064028d2f2d911398012103aef3ce8666342ddfc
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Jacky-Huang/arm64-Kconfig-platforms-Add-config-for-Nuvoton-MA35-platform/20230315-153355
git checkout 064028d2f2d911398012103aef3ce8666342ddfc
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc SHELL=/bin/bash drivers/tty/serial/
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
>> drivers/tty/serial/ma35d1_serial.c:672:6: warning: no previous prototype for 'ma35d1serial_suspend_port' [-Wmissing-prototypes]
672 | void ma35d1serial_suspend_port(int line)
| ^~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/tty/serial/ma35d1_serial.c:681:6: warning: no previous prototype for 'ma35d1serial_resume_port' [-Wmissing-prototypes]
681 | void ma35d1serial_resume_port(int line)
| ^~~~~~~~~~~~~~~~~~~~~~~~
--
>> drivers/tty/serial/ma35d1_serial.c:670: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Suspend one serial port.
drivers/tty/serial/ma35d1_serial.c:679: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Resume one serial port.
vim +/ma35d1serial_suspend_port +672 drivers/tty/serial/ma35d1_serial.c
668
669 /**
> 670 * Suspend one serial port.
671 */
> 672 void ma35d1serial_suspend_port(int line)
673 {
674 uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
675 }
676 EXPORT_SYMBOL(ma35d1serial_suspend_port);
677
678 /**
679 * Resume one serial port.
680 */
> 681 void ma35d1serial_resume_port(int line)
682 {
683 struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
684
685 uart_resume_port(&ma35d1serial_reg, &up->port);
686 }
687 EXPORT_SYMBOL(ma35d1serial_resume_port);
688
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests
On 15. 03. 23, 8:29, Jacky Huang wrote:
> --- /dev/null
> +++ b/drivers/tty/serial/ma35d1_serial.c
> @@ -0,0 +1,842 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * MA35D1 serial driver
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
What parameters does this module have?
> +#include <linux/ioport.h>
> +#include <linux/init.h>
> +#include <linux/console.h>
> +#include <linux/sysrq.h>
> +#include <linux/delay.h>
What do you use from delay.h?
> +#include <linux/platform_device.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/clk.h>
> +#include <linux/serial_reg.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial.h>
> +#include <linux/nmi.h>
nmi.h?
Please clean up all of the includes.
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/io.h>
> +#include <asm/irq.h>
> +#include <asm/serial.h>
> +#include "ma35d1_serial.h"
> +
> +#define UART_NR 17
> +
> +static struct uart_driver ma35d1serial_reg;
> +struct clk *clk;
> +
> +struct uart_ma35d1_port {
> + struct uart_port port;
> + u16 capabilities; /* port capabilities */
> + u8 ier;
> + u8 lcr;
> + u8 mcr;
> + u8 mcr_mask; /* mask of user bits */
> + u8 mcr_force; /* mask of forced bits */
Where are all those used?
> + struct serial_rs485 rs485; /* rs485 settings */
> + u32 baud_rate;
And this one.
> + int rx_count;
> + u32 console_baud_rate;
> + u32 console_line;
> + u32 console_int;
> +};
> +
> +static struct device_node *ma35d1serial_uart_nodes[UART_NR];
> +static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
> +static void __stop_tx(struct uart_ma35d1_port *p);
What for?
> +static void transmit_chars(struct uart_ma35d1_port *up);
> +
> +static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port *uart)
> +{
> + return container_of(uart, struct uart_ma35d1_port, port);
> +}
> +
> +static u32 serial_in(struct uart_ma35d1_port *p, int offset)
Q: int? A: No.
> +{
> + return __raw_readl(p->port.membase + offset);
> +}
> +
> +static void serial_out(struct uart_ma35d1_port *p, int offset, int value)
No ints here, please.
> +{
> + __raw_writel(value, p->port.membase + offset);
> +}
> +
> +static void __stop_tx(struct uart_ma35d1_port *p)
> +{
> + u32 ier;
> +
> + ier = serial_in(p, UART_REG_IER);
> + if (ier & THRE_IEN)
> + serial_out(p, UART_REG_IER, ier & ~THRE_IEN);
> +}
> +
> +static void ma35d1serial_stop_tx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
Despite you have to_ma35d1_uart_port(), you do this?
> +
> + __stop_tx(up);
> +}
> +
> +static void ma35d1serial_start_tx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 ier;
> + struct circ_buf *xmit = &up->port.state->xmit;
> +
> + ier = serial_in(up, UART_REG_IER);
> + serial_out(up, UART_REG_IER, ier & ~THRE_IEN);
> + if (uart_circ_chars_pending(xmit) <
> + (16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0x3F)))
You look like you need a helper for this computation (hint: GENMASK()).
What do those magic constants mean?
> + transmit_chars(up);
> + serial_out(up, UART_REG_IER, ier | THRE_IEN);
> +}
> +
> +static void ma35d1serial_stop_rx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
Bah. Nah.
> +
> + serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) & ~RDA_IEN);
> +}
> +
> +static void
> +receive_chars(struct uart_ma35d1_port *up)
> +{
> + u8 ch;
> + u32 fsr;
> + u32 isr;
> + u32 dcnt;
> + char flag;
flag is u8 too. And a reverse xmas tree, please. Actually, you can put
all those u32 to a single line.
> +
> + isr = serial_in(up, UART_REG_ISR);
> + fsr = serial_in(up, UART_REG_FSR);
> +
> + while (!(fsr & RX_EMPTY)) {
> + flag = TTY_NORMAL;
> + up->port.icount.rx++;
> +
> + if (unlikely(fsr & (BIF | FEF | PEF | RX_OVER_IF))) {
> + if (fsr & BIF) {
> + serial_out(up, UART_REG_FSR, BIF);
> + up->port.icount.brk++;
> + if (uart_handle_break(&up->port))
> + continue;
> + }
> + if (fsr & FEF) {
> + serial_out(up, UART_REG_FSR, FEF);
> + up->port.icount.frame++;
> + }
> + if (fsr & PEF) {
> + serial_out(up, UART_REG_FSR, PEF);
> + up->port.icount.parity++;
> + }
> + if (fsr & RX_OVER_IF) {
> + serial_out(up, UART_REG_FSR, RX_OVER_IF);
> + up->port.icount.overrun++;
> + }
> + if (fsr & BIF)
> + flag = TTY_BREAK;
> + if (fsr & PEF)
> + flag = TTY_PARITY;
> + if (fsr & FEF)
> + flag = TTY_FRAME;
> + }
> + ch = (u8)serial_in(up, UART_REG_RBR);
> + if (uart_handle_sysrq_char(&up->port, ch))
> + continue;
> +
> + uart_insert_char(&up->port, fsr, RX_OVER_IF, ch, flag);
No lock needed?
> + up->rx_count++;
> + dcnt = (serial_in(up, UART_REG_FSR) >> 8) & 0x3f;
More magic constants. No.
> + if (up->rx_count > 1023) {
> + spin_lock(&up->port.lock);
> + tty_flip_buffer_push(&up->port.state->port);
> + spin_unlock(&up->port.lock);
> + up->rx_count = 0;
> + if ((isr & RXTO_IF) && (dcnt == 0))
> + goto tout_end;
> + }
> + if (isr & RDA_IF) {
> + if (dcnt == 1)
> + return;
> + }
> + fsr = serial_in(up, UART_REG_FSR);
> + }
> + spin_lock(&up->port.lock);
> + tty_flip_buffer_push(&up->port.state->port);
> + spin_unlock(&up->port.lock);
> +tout_end:
> + up->rx_count = 0;
> +}
> +
> +static void transmit_chars(struct uart_ma35d1_port *up)
Why this cannot use uart_port_tx()?
> +{
> + struct circ_buf *xmit = &up->port.state->xmit;
> + int count = 16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0xF);
> +
> + if (serial_in(up, UART_REG_FSR) & TX_FULL)
> + count = 0;
> + if (up->port.x_char) {
> + serial_out(up, UART_REG_THR, up->port.x_char);
> + up->port.icount.tx++;
> + up->port.x_char = 0;
> + return;
> + }
> + if (uart_tx_stopped(&up->port)) {
> + ma35d1serial_stop_tx(&up->port);
> + return;
> + }
> + if (uart_circ_empty(xmit)) {
> + __stop_tx(up);
> + return;
> + }
> + while (count > 0) {
> + serial_out(up, UART_REG_THR, xmit->buf[xmit->tail]);
> + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
> + up->port.icount.tx++;
> + count--;
> + if (uart_circ_empty(xmit))
> + break;
> + }
> + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> + uart_write_wakeup(&up->port);
> + if (uart_circ_empty(xmit))
> + __stop_tx(up);
> +}
> +
> +static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)dev_id;
> + u32 isr, fsr;
> +
> + isr = serial_in(up, UART_REG_ISR);
> + fsr = serial_in(up, UART_REG_FSR);
> + if (isr & (RDA_IF | RXTO_IF))
> + receive_chars(up);
> + if (isr & THRE_INT)
> + transmit_chars(up);
> + if (fsr & (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF))
> + serial_out(up, UART_REG_FSR,
> + (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF));
> +
> + return IRQ_HANDLED;
You give no way for OS to disable the irq when the HW goes crazy. I.e.
you should return IRQ_HANDLED only when you really handled the irq.
> +}
...
> +static u32 ma35d1serial_get_mctrl(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 status;
> + u32 ret = 0;
> +
> + status = serial_in(up, UART_REG_MSR);
> + if (!(status & 0x10))
0x10 is magic.
> + ret |= TIOCM_CTS;
> + return ret;
> +}
> +
> +static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 mcr = 0;
> + u32 ier = 0;
> +
> + if (mctrl & TIOCM_RTS) {
> + /* set RTS high level trigger */
> + mcr = serial_in(up, UART_REG_MCR);
> + mcr |= 0x200;
> + mcr &= ~(0x2);
> + }
> + if (up->mcr & UART_MCR_AFE) {
> + /* set RTS high level trigger */
> + mcr = serial_in(up, UART_REG_MCR);
> + mcr |= 0x200;
> + mcr &= ~(0x2);
This is repeated. Parentheses are superfluous. And again, 0x200, 0x2 are
magic.
> +
> + /* enable CTS/RTS auto-flow control */
> + serial_out(up, UART_REG_IER,
> + (serial_in(up, UART_REG_IER) | (0x3000)));
> +
> + /* Set hardware flow control */
> + up->port.flags |= UPF_HARD_FLOW;
> + } else {
> + /* disable CTS/RTS auto-flow control */
> + ier = serial_in(up, UART_REG_IER);
> + ier &= ~(0x3000);
Detto.
> + serial_out(up, UART_REG_IER, ier);
> +
> + /* un-set hardware flow control */
> + up->port.flags &= ~UPF_HARD_FLOW;
> + }
> +
> + /* set CTS high level trigger */
> + serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) | (0x100)));
> + serial_out(up, UART_REG_MCR, mcr);
> +}
...
> +static int ma35d1serial_startup(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + struct tty_struct *tty = port->state->port.tty;
> + int retval;
> +
> + /* Reset FIFO */
> + serial_out(up, UART_REG_FCR, TFR | RFR /* | RX_DIS */);
So why not RX_DIS?
> +
> + /* Clear pending interrupts */
> + serial_out(up, UART_REG_ISR, 0xFFFFFFFF);
> +
> + retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
> + tty ? tty->name : "ma35d1_serial", port);
> + if (retval) {
> + dev_err(up->port.dev, "request irq failed.\n");
> + return retval;
> + }
> +
> + /* Now, initialize the UART */
> + /* FIFO trigger level 4 byte */
> + /* RTS trigger level 8 bytes */
> + serial_out(up, UART_REG_FCR,
> + serial_in(up, UART_REG_FCR) | 0x10 | 0x20000);
> + serial_out(up, UART_REG_LCR, 0x7); /* 8 bit */
> + serial_out(up, UART_REG_TOR, 0x40);
You know what.
> + serial_out(up, UART_REG_IER,
> + RTO_IEN | RDA_IEN | TIME_OUT_EN | BUFERR_IEN);
> + return 0;
> +}
> +
> +static void ma35d1serial_shutdown(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + free_irq(port->irq, port);
> +
> + /* Disable interrupts from this port */
> + serial_out(up, UART_REG_IER, 0);
The two lines are switched, IMO. First disable HW, then let the ISR
finish and free it.
> +}
> +
> +static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
> +{
> + u32 quot;
> +
> + quot = (port->uartclk / baud) - 2;
> + return quot;
quot variable is completely superfluous.
> +}
> +
> +static void ma35d1serial_set_termios(struct uart_port *port,
> + struct ktermios *termios,
> + const struct ktermios *old)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 lcr = 0;
> + unsigned long flags;
> + u32 baud, quot;
> +
> + switch (termios->c_cflag & CSIZE) {
> + case CS5:
> + lcr = 0;
> + break;
> + case CS6:
> + lcr |= 1;
> + break;
> + case CS7:
> + lcr |= 2;
> + break;
> + case CS8:
> + default:
> + lcr |= 3;
> + break;
> + }
IOW:
lcr = UART_LCR_WLEN(tty_get_char_size(termios->c_cflag));
> +
> + if (termios->c_cflag & CSTOPB)
> + lcr |= NSB;
> + if (termios->c_cflag & PARENB)
> + lcr |= PBE;
> + if (!(termios->c_cflag & PARODD))
> + lcr |= EPE;
> + if (termios->c_cflag & CMSPAR)
> + lcr |= SPE;
> +
> + baud = uart_get_baud_rate(port, termios, old, port->uartclk / 0xffff,
> + port->uartclk / 11);
> +
> + quot = ma35d1serial_get_divisor(port, baud);
> +
> + /*
> + * Ok, we're now changing the port state. Do it with
> + * interrupts disabled.
> + */
> + spin_lock_irqsave(&up->port.lock, flags);
> +
> + up->port.read_status_mask = RX_OVER_IF;
> + if (termios->c_iflag & INPCK)
> + up->port.read_status_mask |= FEF | PEF;
> + if (termios->c_iflag & (BRKINT | PARMRK))
> + up->port.read_status_mask |= BIF;
> +
> + /*
> + * Characteres to ignore
> + */
> + up->port.ignore_status_mask = 0;
> + if (termios->c_iflag & IGNPAR)
> + up->port.ignore_status_mask |= FEF | PEF;
> + if (termios->c_iflag & IGNBRK) {
> + up->port.ignore_status_mask |= BIF;
> + /*
> + * If we're ignoring parity and break indicators,
> + * ignore overruns too (for real raw support).
> + */
> + if (termios->c_iflag & IGNPAR)
> + up->port.ignore_status_mask |= RX_OVER_IF;
> + }
> + if (termios->c_cflag & CRTSCTS)
> + up->mcr |= UART_MCR_AFE;
> + else
> + up->mcr &= ~UART_MCR_AFE;
> +
> + ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
> + serial_out(up, UART_REG_BAUD, quot | 0x30000000);
> + serial_out(up, UART_REG_LCR, lcr);
> + spin_unlock_irqrestore(&up->port.lock, flags);
> +}
...
> +static void ma35d1serial_config_port(struct uart_port *port, int flags)
> +{
> + int ret;
> +
> + /*
> + * Find the region that we can probe for. This in turn
> + * tells us whether we can probe for the type of port.
> + */
> + ret = ma35d1serial_request_port(port);
> + if (ret < 0)
> + return;
ma35d1serial_request_port() does nothing. You can remove it altogether.
> + port->type = PORT_MA35D1;
> +}
> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
> +{
> + switch (cmd) {
> + default:
> + return -ENOIOCTLCMD;
> + }
> + return 0;
> +}
Drop that completely.
> +static void
> +ma35d1serial_console_init_port(void)
> +{
> + int i = 0;
> + struct device_node *np;
> +
> + for_each_matching_node(np, ma35d1_serial_of_match) {
> + if (ma35d1serial_uart_nodes[i] == NULL) {
> + ma35d1serial_uart_nodes[i] = np;
> + i++;
Unless the dt is broken, this is OK. But I would add a sanity check to i.
> + }
> + }
> +}
...
> +/*
> + * Register a set of serial devices attached to a platform device.
> + * The list is terminated with a zero flags entry, which means we expect
> + * all entries to have at least UPF_BOOT_AUTOCONF set.
> + */
> +static int ma35d1serial_probe(struct platform_device *pdev)
> +{
> + struct resource *res_mem;
> + struct uart_ma35d1_port *up;
> + int ret;
> + struct clk *clk;
> + int err;
> +
> + if (pdev->dev.of_node) {
> + ret = of_alias_get_id(pdev->dev.of_node, "serial");
> + if (ret < 0) {
> + dev_err(&pdev->dev,
> + "failed to get alias/pdev id, errno %d\n",
> + ret);
> + return ret;
> + }
> + }
> + up = &ma35d1serial_ports[ret];
> + up->port.line = ret;
> + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res_mem)
> + return -ENODEV;
> +
> + up->port.iobase = res_mem->start;
> + up->port.membase = ioremap(up->port.iobase, 0x10000);
> + up->port.ops = &ma35d1serial_ops;
> +
> + spin_lock_init(&up->port.lock);
> +
> + clk = of_clk_get(pdev->dev.of_node, 0);
> + if (IS_ERR(clk)) {
> + err = PTR_ERR(clk);
> + dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
> + return -ENOENT;
> + }
> + err = clk_prepare_enable(clk);
> + if (err)
> + return -ENOENT;
> +
> + if (up->port.line != 0)
> + up->port.uartclk = clk_get_rate(clk);
> + up->port.irq = platform_get_irq(pdev, 0);
> + up->port.dev = &pdev->dev;
> + up->port.flags = UPF_BOOT_AUTOCONF;
> + up->port.rs485_config = ma35d1serial_config_rs485;
> + ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
What if this fails?
> + platform_set_drvdata(pdev, up);
> + return 0;
> +}
> +
> +/*
> + * Remove serial ports registered against a platform device.
> + */
> +static int ma35d1serial_remove(struct platform_device *dev)
> +{
> + int i;
> + struct uart_port *port = platform_get_drvdata(dev);
> +
> + free_irq(port->irq, port);
Hmm, this doesn't look right. You did that already, or?
> + for (i = 0; i < UART_NR; i++) {
> + struct uart_ma35d1_port *up = &ma35d1serial_ports[i];
> +
> + if (up->port.dev == &dev->dev)
You did platform_set_drvdata(), so why all this?
> + uart_remove_one_port(&ma35d1serial_reg, &up->port);
> + }
> + return 0;
> +}
regards,
--
js
suse labs
Quoting Jacky Huang (2023-03-15 00:28:55)
> diff --git a/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
> new file mode 100644
> index 000000000000..5c2dea071b38
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
> @@ -0,0 +1,83 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/nuvoton,ma35d1-clk.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Nuvoton MA35D1 Clock Controller Module Binding
Binding is redundant.
> +
> +maintainers:
> + - Chi-Fang Li <[email protected]>
> + - Jacky Huang <[email protected]>
> +
> +description: |
> + The MA35D1 clock controller generates clocks for the whole chip,
> + including system clocks and all peripheral clocks.
> +
> + See also:
> + include/dt-bindings/clock/ma35d1-clk.h
> +
> +properties:
> + compatible:
> + items:
> + - const: nuvoton,ma35d1-clk
> + - const: syscon
Does it really need to be a syscon?
> +
> + reg:
> + maxItems: 1
> +
> + "#clock-cells":
> + const: 1
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + const: clk_hxt
> +
> + assigned-clocks:
> + maxItems: 5
> +
> + assigned-clock-rates:
> + maxItems: 5
I hope the assigned clocks properties can be left out of this doc.
> +
> + nuvoton,pll-mode:
> + description:
> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
> + EPLL, and VPLL in sequential. The operation mode value 0 is for
> + integer mode, 1 is for fractional mode, and 2 is for spread
> + spectrum mode.
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + maxItems: 5
> + items:
> + minimum: 0
> + maximum: 2
Why not use a string?
> +
> + nuvoton,sys:
> + description:
> + Phandle to the system management controller.
> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
> +
> +required:
> + - compatible
> + - reg
> + - "#clock-cells"
> + - clocks
> + - clock-names
> + - nuvoton,sys
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
No need to include this header right?
> +
> + clk: clock-controller@40460200 {
Drop the label?
> + compatible = "nuvoton,ma35d1-clk", "syscon";
> + reg = <0x40460200 0x100>;
> + #clock-cells = <1>;
> + clocks = <&clk_hxt>;
> + clock-names = "clk_hxt";
> + nuvoton,sys = <&sys>;
> + };
Hi Jacky,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on robh/for-next]
[also build test WARNING on clk/clk-next tty/tty-testing tty/tty-next tty/tty-linus linus/master v6.3-rc2 next-20230315]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Jacky-Huang/arm64-Kconfig-platforms-Add-config-for-Nuvoton-MA35-platform/20230315-153355
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20230315072902.9298-13-ychuang570808%40gmail.com
patch subject: [PATCH 12/15] clk: nuvoton: Add clock driver for ma35d1 clock controller
config: arm64-allyesconfig (https://download.01.org/0day-ci/archive/20230316/[email protected]/config)
compiler: aarch64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/696d8d2916e32766dba52bc51453176af883ae96
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Jacky-Huang/arm64-Kconfig-platforms-Add-config-for-Nuvoton-MA35-platform/20230315-153355
git checkout 696d8d2916e32766dba52bc51453176af883ae96
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm64 olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arm64 SHELL=/bin/bash drivers/clk/nuvoton/
If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:79:15: warning: no previous prototype for 'CLK_GetPLLFreq_SMICPLL' [-Wmissing-prototypes]
79 | unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
| ^~~~~~~~~~~~~~~~~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:104:15: warning: no previous prototype for 'CLK_CalPLLFreq_Mode0' [-Wmissing-prototypes]
104 | unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
| ^~~~~~~~~~~~~~~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:185:15: warning: no previous prototype for 'CLK_CalPLLFreq_Mode1' [-Wmissing-prototypes]
185 | unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
| ^~~~~~~~~~~~~~~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:231:15: warning: no previous prototype for 'CLK_CalPLLFreq_Mode2' [-Wmissing-prototypes]
231 | unsigned long CLK_CalPLLFreq_Mode2(unsigned long PllSrcClk,
| ^~~~~~~~~~~~~~~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:305:15: warning: no previous prototype for 'CLK_SetPLLFreq' [-Wmissing-prototypes]
305 | unsigned long CLK_SetPLLFreq(struct ma35d1_clk_pll *pll,
| ^~~~~~~~~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:347:15: warning: no previous prototype for 'CLK_GetPLLFreq_VSIPLL' [-Wmissing-prototypes]
347 | unsigned long CLK_GetPLLFreq_VSIPLL(struct ma35d1_clk_pll *pll,
| ^~~~~~~~~~~~~~~~~~~~~
drivers/clk/nuvoton/clk-ma35d1-pll.c: In function 'CLK_GetPLLFreq_VSIPLL':
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:350:44: warning: variable 'u32FMOD' set but not used [-Wunused-but-set-variable]
350 | u32 u32M, u32N, u32P, u32X, u32SR, u32FMOD;
| ^~~~~~~
>> drivers/clk/nuvoton/clk-ma35d1-pll.c:350:37: warning: variable 'u32SR' set but not used [-Wunused-but-set-variable]
350 | u32 u32M, u32N, u32P, u32X, u32SR, u32FMOD;
| ^~~~~
vim +/CLK_GetPLLFreq_SMICPLL +79 drivers/clk/nuvoton/clk-ma35d1-pll.c
77
78 /* SMIC PLL for CAPLL */
> 79 unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
80 unsigned long PllSrcClk)
81 {
82 u32 u32M, u32N, u32P, u32OutDiv;
83 u32 val;
84 unsigned long u64PllClk;
85 u32 clk_div_table[] = { 1, 2, 4, 8};
86
87 val = __raw_readl(pll->ctl0_base);
88
89 u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
90 u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
91 u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
92 u32OutDiv = clk_div_table[u32P];
93
94 if (val & PLL0CTL0_BP_MSK) {
95 u64PllClk = PllSrcClk;
96 } else {
97 u64PllClk = PllSrcClk * u32N;
98 do_div(u64PllClk, u32M * u32OutDiv);
99 }
100 return u64PllClk;
101 }
102
103 /* VSI-PLL: INTEGER_MODE */
> 104 unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
105 unsigned long u64PllFreq, u32 *u32Reg)
106 {
107 u32 u32TmpM, u32TmpN, u32TmpP;
108 u32 u32RngMinN, u32RngMinM, u32RngMinP;
109 u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
110 u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
111 unsigned long u64PllClk;
112 unsigned long u64Con1, u64Con2, u64Con3;
113
114 u64PllClk = 0;
115 u32Min = (u32) -1;
116
117 if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
118 (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
119 u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
120 u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
121 u64PllClk = ma35d1pll_freq[0].freq;
122 return u64PllClk;
123 }
124
125 u32RngMinM = 1UL;
126 u32RngMaxM = 63UL;
127 u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
128 (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
129 u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
130 (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
131
132 for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
133 u64Con1 = PllSrcClk / u32TmpM;
134 u32RngMinN = 16UL;
135 u32RngMaxN = 2047UL;
136 u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
137 (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
138 u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
139 (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
140
141 for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
142 u32TmpN++) {
143 u64Con2 = u64Con1 * u32TmpN;
144 u32RngMinP = 1UL;
145 u32RngMaxP = 7UL;
146 u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
147 (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
148 u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
149 u32RngMaxP) ?
150 (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
151 u32RngMaxP;
152 for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
153 u32TmpP++) {
154 u64Con3 = u64Con2 / u32TmpP;
155 if (u64Con3 > u64PllFreq)
156 u32Tmp = u64Con3 - u64PllFreq;
157 else
158 u32Tmp = u64PllFreq - u64Con3;
159
160 if (u32Tmp < u32Min) {
161 u32Min = u32Tmp;
162 u32MinM = u32TmpM;
163 u32MinN = u32TmpN;
164 u32MinP = u32TmpP;
165
166 if (u32Min == 0UL) {
167 u32Reg[0] = (u32MinM << 12) |
168 (u32MinN);
169 u32Reg[1] = (u32MinP << 4);
170 return ((PllSrcClk * u32MinN) /
171 (u32MinP * u32MinM));
172 }
173 }
174 }
175 }
176 }
177
178 u32Reg[0] = (u32MinM << 12) | (u32MinN);
179 u32Reg[1] = (u32MinP << 4);
180 u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
181 return u64PllClk;
182 }
183
184 /* VSI-PLL: FRACTIONAL_MODE */
> 185 unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
186 unsigned long u64PllFreq, u32 *u32Reg)
187 {
188 unsigned long u64X, u64N, u64M, u64P, u64tmp;
189 unsigned long u64PllClk, u64FCLKO;
190 u32 u32FRAC;
191
192 if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
193 u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
194 u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
195 u64PllClk = ma35d1pll_freq[1].freq;
196 return u64PllClk;
197 }
198
199 if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
200 u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
201 ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
202 } else {
203 pr_err("Failed to set rate %ld\n", u64PllFreq);
204 return 0;
205 }
206
207 u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
208 ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
209 ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
210
211 if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
212 (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
213 return 0;
214
215 u64M = (PllSrcClk <= VSIPLL_FREFDIVM_MAX_FREQ) ? 1 :
216 ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) +
217 ((PllSrcClk % VSIPLL_FREFDIVM_MAX_FREQ) ? 1 : 0));
218
219 u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
220 u64N = u64tmp / 1000;
221 u64X = u64tmp % 1000;
222 u32FRAC = ((u64X << 24) + 500) / 1000;
223 u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
224
225 u32Reg[0] = (u64M << 12) | (u64N);
226 u32Reg[1] = (u64P << 4) | (u32FRAC << 8);
227 return u64PllClk;
228 }
229
230 /* VSI-PLL: SS_MODE */
> 231 unsigned long CLK_CalPLLFreq_Mode2(unsigned long PllSrcClk,
232 unsigned long u64PllFreq,
233 u32 u32SR, u32 u32Fmod, u32 *u32Reg)
234 {
235 unsigned long u64X, u64N, u64M, u64P, u64tmp, u64tmpP, u64tmpM;
236 unsigned long u64SSRATE, u64SLOPE, u64PllClk, u64FCLKO;
237 u32 u32FRAC, i;
238
239 if (u64PllFreq >= VSIPLL_FCLKO_MAX_FREQ) {
240 u32Reg[0] = ma35d1pll_freq[2].ctl0_reg;
241 u32Reg[1] = ma35d1pll_freq[2].ctl1_reg;
242 u32Reg[2] = ma35d1pll_freq[2].ctl2_reg;
243 u64PllClk = ma35d1pll_freq[2].freq;
244 return u64PllClk;
245 }
246
247 if (u64PllFreq < VSIPLL_FCLKO_MIN_FREQ) {
248 u64FCLKO = 0;
249 for (i = 2; i < 8; i++) {
250 u64tmp = (i * u64PllFreq);
251 if (u64tmp > VSIPLL_FCLKO_MIN_FREQ)
252 u64FCLKO = u64tmp;
253 }
254 if (u64FCLKO == 0) {
255 pr_err("Failed to set rate %ld\n", u64PllFreq);
256 return 0;
257 }
258
259 } else
260 u64FCLKO = u64PllFreq;
261
262 u64P = 0;
263 for (i = 1; i < 8; i++) {
264 u64tmpP = i * u64FCLKO;
265 if ((u64tmpP <= VSIPLL_FCLK_MAX_FREQ) &&
266 (u64tmpP >= VSIPLL_FCLK_MIN_FREQ)) {
267 u64P = i;
268 break;
269 }
270 }
271
272 if (u64P == 0)
273 return 0;
274
275 u64M = 0;
276 for (i = 1; i < 64; i++) {
277 u64tmpM = PllSrcClk / i;
278 if ((u64tmpM <= VSIPLL_FREFDIVM_MAX_FREQ) &&
279 (u64tmpM >= VSIPLL_FREFDIVM_MIN_FREQ1)) {
280 u64M = i;
281 break;
282 }
283 }
284
285 if (u64M == 0)
286 return 0;
287
288 u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
289 u64N = u64tmp / 1000;
290 u64X = u64tmp % 1000;
291 u32FRAC = ((u64X << 24) + 500) / 1000;
292
293 u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
294 u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
295
296 u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
297
298 u32Reg[0] = (u64SSRATE << VSIPLLCTL0_SSRATE_POS) | (u64M <<
299 VSIPLLCTL0_INDIV_POS) | (u64N);
300 u32Reg[1] = (u64P << VSIPLLCTL1_OUTDIV_POS) | (u32FRAC << VSIPLLCTL1_FRAC_POS);
301 u32Reg[2] = u64SLOPE;
302 return u64PllClk;
303 }
304
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests
Quoting Jacky Huang (2023-03-15 00:28:59)
> diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c
> new file mode 100644
> index 000000000000..5f4791531e47
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c
> @@ -0,0 +1,144 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/spinlock.h>
> +
> +#include "clk-ma35d1.h"
> +
> +#define div_mask(width) ((1 << (width)) - 1)
This is clk_div_mask()
> +
> +struct ma35d1_adc_clk_divider {
> + struct clk_hw hw;
> + void __iomem *reg;
> + u8 shift;
> + u8 width;
> + u32 mask;
> + const struct clk_div_table *table;
> + spinlock_t *lock;
> +};
> +
> +#define to_ma35d1_adc_clk_divider(_hw) \
> + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
> +
> +static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + unsigned int val;
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + val = readl_relaxed(dclk->reg) >> dclk->shift;
> + val &= div_mask(dclk->width);
> + val += 1;
> + return divider_recalc_rate(hw, parent_rate, val, dclk->table,
> + CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
> +}
> +
> +static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + return divider_round_rate(hw, rate, prate, dclk->table,
> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
> +}
> +
> +static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + int value;
> + unsigned long flags = 0;
> + u32 data;
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + value = divider_get_val(rate, parent_rate, dclk->table,
> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
> +
> + if (dclk->lock)
> + spin_lock_irqsave(dclk->lock, flags);
> +
> + data = readl_relaxed(dclk->reg);
> + data &= ~(div_mask(dclk->width) << dclk->shift);
> + data |= (value - 1) << dclk->shift;
> + data |= dclk->mask;
> +
> + writel_relaxed(data, dclk->reg);
> +
> + if (dclk->lock)
> + spin_unlock_irqrestore(dclk->lock, flags);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops ma35d1_adc_clkdiv_ops = {
> + .recalc_rate = ma35d1_clkdiv_recalc_rate,
> + .round_rate = ma35d1_clkdiv_round_rate,
> + .set_rate = ma35d1_clkdiv_set_rate,
> +};
> +
> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
> + const char *parent_name,
> + unsigned long flags, void __iomem *reg,
> + u8 shift, u8 width, u32 mask_bit)
> +{
> + struct ma35d1_adc_clk_divider *div;
> + struct clk_init_data init;
> + struct clk_div_table *table;
> + u32 max_div, min_div;
> + struct clk_hw *hw;
> + int ret;
> + int i;
> +
> + /* allocate the divider */
Please remove useless comment.
> + div = kzalloc(sizeof(*div), GFP_KERNEL);
> + if (!div)
> + return ERR_PTR(-ENOMEM);
> +
> + /* Init the divider table */
Please remove useless comment.
> + max_div = div_mask(width) + 1;
> + min_div = 1;
> +
> + table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
Use devm_ allocations please.
> + if (!table) {
> + kfree(div);
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + for (i = 0; i < max_div; i++) {
> + table[i].val = (min_div + i);
> + table[i].div = 2 * table[i].val;
> + }
> + table[max_div].val = 0;
> + table[max_div].div = 0;
> +
> + init.name = name;
> + init.ops = &ma35d1_adc_clkdiv_ops;
> + init.flags |= flags;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.num_parents = parent_name ? 1 : 0;
> +
> + /* struct ma35d1_adc_clk_divider assignments */
Please remove useless comment.
> + div->reg = reg;
> + div->shift = shift;
> + div->width = width;
> + div->mask = mask_bit ? BIT(mask_bit) : 0;
> + div->lock = &ma35d1_lock;
> + div->hw.init = &init;
> + div->table = table;
> +
> + /* Register the clock */
Please remove useless comment.
> + hw = &div->hw;
> + ret = clk_hw_register(NULL, hw);
Use devm_clk_hw_register()
> + if (ret) {
> + kfree(table);
> + kfree(div);
> + return ERR_PTR(ret);
> + }
> + return hw;
> +}
> diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> new file mode 100644
> index 000000000000..79e724b148fa
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> @@ -0,0 +1,534 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk.h>
Do you need to include this header?
> +#include <linux/clk-provider.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/bitfield.h>
> +
> +#include "clk-ma35d1.h"
> +
> +#define to_ma35d1_clk_pll(clk) \
> + (container_of(clk, struct ma35d1_clk_pll, clk))
> +
> +#define PLL0CTL0_FBDIV_MSK GENMASK(7, 0)
> +#define PLL0CTL0_INDIV_MSK GENMASK(11, 8)
> +#define PLL0CTL0_OUTDIV_MSK GENMASK(13, 12)
> +#define PLL0CTL0_PD_MSK BIT(16)
> +#define PLL0CTL0_BP_MSK BIT(17)
> +#define PLLXCTL0_FBDIV_MSK GENMASK(10, 0)
> +#define PLLXCTL0_INDIV_MSK GENMASK(17, 12)
> +#define PLLXCTL0_MODE_MSK GENMASK(19, 18)
> +#define PLLXCTL0_SSRATE_MSK GENMASK(30, 20)
> +#define PLLXCTL1_PD_MSK BIT(0)
> +#define PLLXCTL1_BP_MSK BIT(1)
> +#define PLLXCTL1_OUTDIV_MSK GENMASK(6, 4)
> +#define PLLXCTL1_FRAC_MSK GENMASK(31, 8)
> +#define PLLXCTL2_SLOPE_MSK GENMASK(23, 0)
> +
> +struct ma35d1_clk_pll {
> + struct clk_hw hw;
> + u8 type;
> + u8 mode;
> + unsigned long rate;
> + void __iomem *ctl0_base;
> + void __iomem *ctl1_base;
> + void __iomem *ctl2_base;
> + struct regmap *regmap;
> +};
> +
> +struct vsipll_freq_conf_reg_tbl {
> + unsigned long freq;
> + u8 mode;
> + u32 ctl0_reg;
> + u32 ctl1_reg;
> + u32 ctl2_reg;
> +};
> +
> +static const struct vsipll_freq_conf_reg_tbl ma35d1pll_freq[] = {
> + { 1000000000, VSIPLL_INTEGER_MODE, 0x307d, 0x10, 0 },
> + { 884736000, VSIPLL_FRACTIONAL_MODE, 0x41024, 0xdd2f1b11, 0 },
> + { 533000000, VSIPLL_SS_MODE, 0x12b8102c, 0x6aaaab20, 0x12317 },
> + { }
> +};
> +
> +static void CLK_UnLockReg(struct ma35d1_clk_pll *pll)
Please don't use a mix of upper and lower case function names.
Everything should be lower case. Maybe the name should be
ma35d1_clk_pll_unlock_reg()
> +{
> + int ret;
> +
> + /* Unlock PLL registers */
> + do {
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x59);
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x16);
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x88);
> + regmap_read(pll->regmap, REG_SYS_RLKTZNS, &ret);
> + } while (ret == 0);
> +}
> +
> +static void CLK_LockReg(struct ma35d1_clk_pll *pll)
> +{
ma35d1_clk_pll_lock_reg()
> + /* Lock PLL registers */
Remove these worthless comments.
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x0);
> +}
> +
> +/* SMIC PLL for CAPLL */
> +unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
> + unsigned long PllSrcClk)
> +{
> + u32 u32M, u32N, u32P, u32OutDiv;
Variable names should not have the type in them. 'm', 'n', 'p', 'div'
should suffice.
> + u32 val;
> + unsigned long u64PllClk;
> + u32 clk_div_table[] = { 1, 2, 4, 8};
> +
> + val = __raw_readl(pll->ctl0_base);
Why do you need to use __raw_readl()? Just use readl() here.
> +
> + u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
> + u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
> + u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
> + u32OutDiv = clk_div_table[u32P];
> +
> + if (val & PLL0CTL0_BP_MSK) {
> + u64PllClk = PllSrcClk;
> + } else {
> + u64PllClk = PllSrcClk * u32N;
> + do_div(u64PllClk, u32M * u32OutDiv);
> + }
Add a newline here.
> + return u64PllClk;
> +}
> +
> +/* VSI-PLL: INTEGER_MODE */
I have no idea what this means.
> +unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
> + unsigned long u64PllFreq, u32 *u32Reg)
Again, don't put types into the variable name.
> +{
> + u32 u32TmpM, u32TmpN, u32TmpP;
> + u32 u32RngMinN, u32RngMinM, u32RngMinP;
> + u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
> + u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
> + unsigned long u64PllClk;
> + unsigned long u64Con1, u64Con2, u64Con3;
My eyes! Seriously, kernel style is not this way. Did checkpatch.pl pass
on this?
> +
> + u64PllClk = 0;
> + u32Min = (u32) -1;
> +
> + if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
> + (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
> + u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
> + u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
> + u64PllClk = ma35d1pll_freq[0].freq;
> + return u64PllClk;
> + }
> +
> + u32RngMinM = 1UL;
> + u32RngMaxM = 63UL;
> + u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
> + (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
> + u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
> + (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
> +
> + for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
> + u64Con1 = PllSrcClk / u32TmpM;
> + u32RngMinN = 16UL;
> + u32RngMaxN = 2047UL;
> + u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
> + (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
> + u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
> + (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
Is this clamp()?
> +
> + for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
> + u32TmpN++) {
> + u64Con2 = u64Con1 * u32TmpN;
> + u32RngMinP = 1UL;
> + u32RngMaxP = 7UL;
> + u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
> + (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
Is this clamp()?
> + u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
> + u32RngMaxP) ?
> + (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
> + u32RngMaxP;
> + for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
> + u32TmpP++) {
> + u64Con3 = u64Con2 / u32TmpP;
> + if (u64Con3 > u64PllFreq)
> + u32Tmp = u64Con3 - u64PllFreq;
> + else
> + u32Tmp = u64PllFreq - u64Con3;
> +
> + if (u32Tmp < u32Min) {
> + u32Min = u32Tmp;
> + u32MinM = u32TmpM;
> + u32MinN = u32TmpN;
> + u32MinP = u32TmpP;
> +
> + if (u32Min == 0UL) {
> + u32Reg[0] = (u32MinM << 12) |
> + (u32MinN);
> + u32Reg[1] = (u32MinP << 4);
> + return ((PllSrcClk * u32MinN) /
> + (u32MinP * u32MinM));
> + }
> + }
> + }
> + }
> + }
It's too hard to read this code.
> +
> + u32Reg[0] = (u32MinM << 12) | (u32MinN);
FIELD_PREP?
> + u32Reg[1] = (u32MinP << 4);
ditto?
> + u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
> + return u64PllClk;
> +}
> +
> +/* VSI-PLL: FRACTIONAL_MODE */
> +unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
> + unsigned long u64PllFreq, u32 *u32Reg)
> +{
> + unsigned long u64X, u64N, u64M, u64P, u64tmp;
> + unsigned long u64PllClk, u64FCLKO;
> + u32 u32FRAC;
> +
> + if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
> + u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
> + u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
> + u64PllClk = ma35d1pll_freq[1].freq;
> + return u64PllClk;
> + }
> +
> + if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
Use a local variable for the right hand side of the comparison.
> + u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
> + ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
Is this DIV_ROUND_UP() or something like that?
> + } else {
> + pr_err("Failed to set rate %ld\n", u64PllFreq);
> + return 0;
> + }
> +
> + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
> + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
> + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
> +
> + if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
> + (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
> + return 0;
> +
[...]
> + break;
> + }
> +
> + return pllfreq;
> +}
> +
> +static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + return rate;
This needs to do actual math and figure out that some rate will not
work and calculate what the rate will actually be if clk_set_rate() is
called with 'rate'.
> +}
> +
> +static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val = __raw_readl(pll->ctl1_base);
> +
> + return (val & VSIPLLCTL1_PD_MSK) ? 0 : 1;
> +}
> +
> +static int ma35d1_clk_pll_prepare(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val;
> +
> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
> + return -EACCES;
> + }
> +
> + CLK_UnLockReg(pll);
> + val = __raw_readl(pll->ctl1_base);
> + val &= ~VSIPLLCTL1_PD_MSK;
> + __raw_writel(val, pll->ctl1_base);
> + CLK_LockReg(pll);
> + return 0;
> +}
> +
> +static void ma35d1_clk_pll_unprepare(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val;
> +
> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
> + } else {
> + val = __raw_readl(pll->ctl1_base);
> + val |= VSIPLLCTL1_PD_MSK;
> + __raw_writel(val, pll->ctl1_base);
> + }
> +}
> +
> +static const struct clk_ops ma35d1_clk_pll_ops = {
> + .is_prepared = ma35d1_clk_pll_is_prepared,
> + .prepare = ma35d1_clk_pll_prepare,
> + .unprepare = ma35d1_clk_pll_unprepare,
> + .set_rate = ma35d1_clk_pll_set_rate,
> + .recalc_rate = ma35d1_clk_pll_recalc_rate,
> + .round_rate = ma35d1_clk_pll_round_rate,
> +};
> +
> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type,
> + u8 u8mode, const char *name,
> + const char *parent,
> + unsigned long targetFreq,
> + void __iomem *base,
> + struct regmap *regmap)
> +{
> + struct ma35d1_clk_pll *pll;
> + struct clk_hw *hw;
> + struct clk_init_data init;
> + int ret;
> +
> + pll = kmalloc(sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + pll->type = type;
> + pll->mode = u8mode;
> + pll->rate = targetFreq;
> + pll->ctl0_base = base + VSIPLL_CTL0;
> + pll->ctl1_base = base + VSIPLL_CTL1;
> + pll->ctl2_base = base + VSIPLL_CTL2;
> + pll->regmap = regmap;
> +
> + init.name = name;
> + init.flags = 0;
> + init.parent_names = &parent;
> + init.num_parents = 1;
> + init.ops = &ma35d1_clk_pll_ops;
> + pll->hw.init = &init;
> + hw = &pll->hw;
> +
> + ret = clk_hw_register(NULL, hw);
> + if (ret) {
> + pr_err("failed to register vsi-pll clock!!!\n");
> + kfree(pll);
> + return ERR_PTR(ret);
> + }
> + return hw;
> +}
> diff --git a/drivers/clk/nuvoton/clk-ma35d1.c b/drivers/clk/nuvoton/clk-ma35d1.c
> new file mode 100644
> index 000000000000..ac8154458b81
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1.c
> @@ -0,0 +1,970 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk.h>
I don't see any clk consumer usage. Please remove.
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
I don't see any clkdev usage. Please remove.
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> +
> +#include "clk-ma35d1.h"
> +
> +DEFINE_SPINLOCK(ma35d1_lock);
Why not static?
> +
> +static const char *const ca35clk_sel_clks[] = {
> + "hxt", "capll", "ddrpll", "dummy"
Are these parent mappings? Please use 'struct clk_parent_data' instead
if so.
> +};
> +
> +static const char *const sysclk0_sel_clks[] = {
> + "epll_div2", "syspll"
> +};
> +
[...]
> +
> +static struct clk_hw **hws;
> +static struct clk_hw_onecell_data *ma35d1_hw_data;
Any reason to make these global pointers vs local pointers during probe?
> +
> +static int ma35d1_clocks_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct device *dev = &pdev->dev;
> + struct device_node *clk_node = dev->of_node;
> + void __iomem *clk_base;
> + struct regmap *regmap;
> + u32 pllmode[5] = { 0, 0, 0, 0, 0 };
> + u32 pllfreq[5] = { 0, 0, 0, 0, 0 };
> +
> + dev_info(&pdev->dev, "Nuvoton MA35D1 Clock Driver\n");
Drop this banner message please.
> + ma35d1_hw_data = devm_kzalloc(&pdev->dev, struct_size(ma35d1_hw_data,
> + hws, CLK_MAX_IDX), GFP_KERNEL);
> +
> + if (WARN_ON(!ma35d1_hw_data))
> + return -ENOMEM;
> +
> + ma35d1_hw_data->num = CLK_MAX_IDX;
> + hws = ma35d1_hw_data->hws;
> +
> + clk_node = of_find_compatible_node(NULL, NULL, "nuvoton,ma35d1-clk");
> + clk_base = of_iomap(clk_node, 0);
Use platform_device APIs as you have a platform device here ('pdev').
> + of_node_put(clk_node);
> + if (!clk_base) {
> + pr_err("%s: could not map region\n", __func__);
> + return -ENOMEM;
> + }
> + regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
> + "nuvoton,sys");
Why is it a syscon?
> + if (IS_ERR(regmap))
> + pr_warn("%s: Unable to get syscon\n", __func__);
How can we continue without the regmap?
> +
> + /* clock sources */
> + hws[HXT] = ma35d1_clk_fixed("hxt", 24000000);
[...]
> + /* EADC */
> + hws[EADC_DIV] = ma35d1_clk_divider_table("eadc_div", "pclk2",
> + clk_base + REG_CLK_CLKDIV4,
> + 0, 4, eadc_div_table);
> + hws[EADC_GATE] = ma35d1_clk_gate("eadc_gate", "eadc_div",
> + clk_base + REG_CLK_APBCLK2, 25);
> +
> + ret = of_clk_add_hw_provider(clk_node, of_clk_hw_onecell_get,
Use devm_ variant.
> + ma35d1_hw_data);
> + if (ret < 0) {
> + dev_err(dev, "failed to register hws for MA35D1\n");
> + iounmap(clk_base);
Use devm mapping APIs to avoid unmapping on error path.
> + }
> + return ret;
> +}
> +
> +static const struct of_device_id ma35d1_clk_of_match[] = {
> + { .compatible = "nuvoton,ma35d1-clk" },
> + { },
Drop comma above so nothing can come after this.
> +};
> +MODULE_DEVICE_TABLE(of, ma35d1_clk_of_match);
> +
> +static struct platform_driver ma35d1_clk_driver = {
> + .probe = ma35d1_clocks_probe,
> + .driver = {
> + .name = "ma35d1-clk",
> + .of_match_table = ma35d1_clk_of_match,
> + },
> +};
> +
> +static int __init ma35d1_clocks_init(void)
> +{
> + return platform_driver_register(&ma35d1_clk_driver);
> +}
> +
> +postcore_initcall(ma35d1_clocks_init);
> +
> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
> new file mode 100644
> index 000000000000..faae5a17e425
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
> @@ -0,0 +1,198 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
> +#define __DRV_CLK_NUVOTON_MA35D1_H
> +
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
Are these includes used?
> +#include <linux/clk-provider.h>
> +#include <linux/spinlock.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/ma35d1-sys.h>
These probably aren't needed to be included here. Just forward declare
structs you need and include the headers in the C file.
> +
[...]
> +
> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev,
> + const char *name,
> + const char *parent_name,
> + unsigned long flags,
> + void __iomem *reg, u8 shift,
> + u8 width, u32 mask_bit);
> +
> +extern spinlock_t ma35d1_lock;
Why?
Dear Stephen,
Thanks for your review.
On 2023/3/16 上午 05:59, Stephen Boyd wrote:
> Quoting Jacky Huang (2023-03-15 00:28:55)
>> diff --git a/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>> new file mode 100644
>> index 000000000000..5c2dea071b38
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>> @@ -0,0 +1,83 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/clock/nuvoton,ma35d1-clk.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Nuvoton MA35D1 Clock Controller Module Binding
> Binding is redundant.
I will remove this word.
>> +
>> +maintainers:
>> + - Chi-Fang Li <[email protected]>
>> + - Jacky Huang <[email protected]>
>> +
>> +description: |
>> + The MA35D1 clock controller generates clocks for the whole chip,
>> + including system clocks and all peripheral clocks.
>> +
>> + See also:
>> + include/dt-bindings/clock/ma35d1-clk.h
>> +
>> +properties:
>> + compatible:
>> + items:
>> + - const: nuvoton,ma35d1-clk
>> + - const: syscon
> Does it really need to be a syscon?
Some registers of the clock controller are locked against writing.
Before writing, the lock must be unlocked through the system controller.
So syscon is needed.
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + "#clock-cells":
>> + const: 1
>> +
>> + clocks:
>> + maxItems: 1
>> +
>> + clock-names:
>> + const: clk_hxt
>> +
>> + assigned-clocks:
>> + maxItems: 5
>> +
>> + assigned-clock-rates:
>> + maxItems: 5
> I hope the assigned clocks properties can be left out of this doc.
Sure, we will remove it.
>> +
>> + nuvoton,pll-mode:
>> + description:
>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>> + integer mode, 1 is for fractional mode, and 2 is for spread
>> + spectrum mode.
>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>> + maxItems: 5
>> + items:
>> + minimum: 0
>> + maximum: 2
> Why not use a string?
OK, we'll use strings instead.
>> +
>> + nuvoton,sys:
>> + description:
>> + Phandle to the system management controller.
>> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - "#clock-cells"
>> + - clocks
>> + - clock-names
>> + - nuvoton,sys
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> No need to include this header right?
Yes, I will remove this header in the next version.
>
>> +
>> + clk: clock-controller@40460200 {
> Drop the label?
OK, I will drop this label in the next version.
>
>> + compatible = "nuvoton,ma35d1-clk", "syscon";
>> + reg = <0x40460200 0x100>;
>> + #clock-cells = <1>;
>> + clocks = <&clk_hxt>;
>> + clock-names = "clk_hxt";
>> + nuvoton,sys = <&sys>;
>> + };
Best Regards,
Jacky Huang
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add the dt-bindings header for Nuvoton ma35d1, that gets shared
> between the clock controller and clock references in the dts.
I don't see the device binding. They come together.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> .../dt-bindings/clock/nuvoton,ma35d1-clk.h | 253 ++++++++++++++++++
> 1 file changed, 253 insertions(+)
> create mode 100644 include/dt-bindings/clock/nuvoton,ma35d1-clk.h
>
> diff --git a/include/dt-bindings/clock/nuvoton,ma35d1-clk.h b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
> new file mode 100644
> index 000000000000..6c569fdd6e06
> --- /dev/null
> +++ b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
Dual license.
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add the dt-bindings header for Nuvoton ma35d1, that gets shared
> between the reset controller and reset references in the dts.
>
> Signed-off-by: Jacky Huang <[email protected]>
Same problems as previous patch.
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add Nuvoton ma35d1 system registers compatible
Missing full stop.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
> index c828c4f5e4a7..e7a3c6e1e77f 100644
> --- a/Documentation/devicetree/bindings/mfd/syscon.yaml
> +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
> @@ -57,6 +57,7 @@ properties:
> - microchip,sparx5-cpu-syscon
> - mstar,msc313-pmsleep
> - nuvoton,wpcm450-shm
> + - nuvoton,ma35d1-sys
Wrong order
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add binding for ARMv8 based Nuvotn SoCs and platform boards.
> Add initial bindings for ma35d1 series development boards.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> .../devicetree/bindings/arm/nuvoton.yaml | 30 +++++++++++++++++++
And what is npcm for? Why it was made an directory?
All these should be just one Nuvoton.
> 1 file changed, 30 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/arm/nuvoton.yaml
>
> diff --git a/Documentation/devicetree/bindings/arm/nuvoton.yaml b/Documentation/devicetree/bindings/arm/nuvoton.yaml
> new file mode 100644
> index 000000000000..f95e7b30711e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/nuvoton.yaml
> @@ -0,0 +1,30 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add documentation to describe nuvoton ma35d1 clock driver bindings.
Subject: drop second/last, redundant "bindings". The "dt-bindings"
prefix is already stating that these are bindings.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> .../bindings/clock/nuvoton,ma35d1-clk.yaml | 83 +++++++++++++++++++
> 1 file changed, 83 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>
> diff --git a/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
> new file mode 100644
> index 000000000000..5c2dea071b38
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
> @@ -0,0 +1,83 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/nuvoton,ma35d1-clk.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Nuvoton MA35D1 Clock Controller Module Binding
> +
> +maintainers:
> + - Chi-Fang Li <[email protected]>
> + - Jacky Huang <[email protected]>
> +
> +description: |
> + The MA35D1 clock controller generates clocks for the whole chip,
> + including system clocks and all peripheral clocks.
> +
> + See also:
> + include/dt-bindings/clock/ma35d1-clk.h
> +
> +properties:
> + compatible:
> + items:
> + - const: nuvoton,ma35d1-clk
> + - const: syscon
> +
> + reg:
> + maxItems: 1
> +
> + "#clock-cells":
> + const: 1
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + const: clk_hxt
Drop clock-names. You do not need it for one clock.
> +
> + assigned-clocks:
> + maxItems: 5
> +
> + assigned-clock-rates:
> + maxItems: 5
Drop both properties, you do not need them in the binding.
> +
> + nuvoton,pll-mode:
> + description:
> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
> + EPLL, and VPLL in sequential. The operation mode value 0 is for
> + integer mode, 1 is for fractional mode, and 2 is for spread
> + spectrum mode.
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + maxItems: 5
> + items:
> + minimum: 0
> + maximum: 2
Why exactly this is suitable for DT?
> +
> + nuvoton,sys:
> + description:
> + Phandle to the system management controller.
> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
Drop quotes.
You need here constraints, look for existing examples.
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add documentation to describe nuvoton ma35d1 reset driver bindings.
Subject: drop second/last, redundant "bindings". The "dt-bindings"
prefix is already stating that these are bindings.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> .../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
> 1 file changed, 50 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>
> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
> new file mode 100644
> index 000000000000..f66c566c6dce
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
> @@ -0,0 +1,50 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Nuvoton MA35D1 Reset Controller
> +
> +maintainers:
> + - Chi-Fang Li <[email protected]>
> + - Jacky Huang <[email protected]>
> +
> +description:
> + The system reset controller can be used to reset various peripheral
> + controllers in MA35D1 SoC.
> +
> +properties:
> + compatible:
> + const: nuvoton,ma35d1-reset
> +
> + regmap:
> + $ref: /schemas/types.yaml#/definitions/phandle
> + description: Phandle to the register map node.
You need to be specific what is this. As you can easily check, there is
no such property in any devices. I don't understand why do you need it
in the first place.
> +
> + '#reset-cells':
> + const: 1
> +
> +required:
> + - compatible
> + - regmap
> + - '#reset-cells'
> +
> +additionalProperties: false
> +
> +examples:
> + # system reset controller node:
> + - |
> + #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
> +
> + sys: system-management@40460000 {
> + compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
And your patchset is not bisectable.... Test for bisectability before
sending.
Best regards,
Krzysztof
On 16/03/2023 08:37, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add documentation to describe nuvoton ma35d1 reset driver bindings.
>
> Subject: drop second/last, redundant "bindings". The "dt-bindings"
> prefix is already stating that these are bindings.
>
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> .../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
>> 1 file changed, 50 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>> new file mode 100644
>> index 000000000000..f66c566c6dce
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>> @@ -0,0 +1,50 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Nuvoton MA35D1 Reset Controller
>> +
>> +maintainers:
>> + - Chi-Fang Li <[email protected]>
>> + - Jacky Huang <[email protected]>
>> +
>> +description:
>> + The system reset controller can be used to reset various peripheral
>> + controllers in MA35D1 SoC.
>> +
>> +properties:
>> + compatible:
>> + const: nuvoton,ma35d1-reset
>> +
>> + regmap:
>> + $ref: /schemas/types.yaml#/definitions/phandle
>> + description: Phandle to the register map node.
>
> You need to be specific what is this. As you can easily check, there is
> no such property in any devices. I don't understand why do you need it
> in the first place.
>
>> +
>> + '#reset-cells':
>> + const: 1
>> +
>> +required:
>> + - compatible
>> + - regmap
>> + - '#reset-cells'
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + # system reset controller node:
>> + - |
>> + #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
>> +
>> + sys: system-management@40460000 {
>> + compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
>
> And your patchset is not bisectable.... Test for bisectability before
> sending.
Ah, no, it's correct. I see the compatible in previous patch. You need
to clearly describe the dependencies and merging strategy/requirements
in cover letter.
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add documentation to describe nuvoton ma35d1 uart driver bindings.
Subject: drop second/last, redundant "bindings". The "dt-bindings"
prefix is already stating that these are bindings.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> .../serial/nuvoton,ma35d1-serial.yaml | 52 +++++++++++++++++++
> 1 file changed, 52 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
>
> diff --git a/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
> new file mode 100644
> index 000000000000..9daa2efd4734
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
> @@ -0,0 +1,52 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/serial/nuvoton,ma35d1-serial.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Nuvoton MA35D1 Universal Asynchronous Receiver/Transmitter (UART)
> +
> +maintainers:
> + - Min-Jen Chen <[email protected]>
> + - Jacky Huang <[email protected]>
> +
> +allOf:
> + - $ref: "serial.yaml"
Drop quotes. Use some recent bindings as your starting point, so we do
not have to give comments for things which were already fixed.
> +
> +properties:
> + compatible:
> + const: nuvoton,ma35d1-uart
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> +
> + aliases {
> + serial0 = &uart0;
> + };
Drop aliases.
> +
> + uart0:serial@40700000 {
Drop label
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This patchset adds initial support for the Nuvoton ma35d1 SoC, including
> initial device tree, clock driver, reset driver, and serial driver.
>
> This patchset cover letter is based from the initial support for Nuvoton
> ma35d1 to keep tracking the version history.
>
To maintainers: patches should not be applied independently because it
will start failing tests...
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add initial device tree support for Nuvoton ma35d1 SoC, including
> cpu, clock, reset, and serial controllers.
> Add reference boards som-256m and iot-512m.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> arch/arm64/boot/dts/nuvoton/Makefile | 2 +
> .../boot/dts/nuvoton/ma35d1-iot-512m.dts | 24 ++
> .../boot/dts/nuvoton/ma35d1-som-256m.dts | 23 ++
> arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 272 ++++++++++++++++++
> 4 files changed, 321 insertions(+)
> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>
> diff --git a/arch/arm64/boot/dts/nuvoton/Makefile b/arch/arm64/boot/dts/nuvoton/Makefile
> index a99dab90472a..c11ab4eac9c7 100644
> --- a/arch/arm64/boot/dts/nuvoton/Makefile
> +++ b/arch/arm64/boot/dts/nuvoton/Makefile
> @@ -1,2 +1,4 @@
> # SPDX-License-Identifier: GPL-2.0
> dtb-$(CONFIG_ARCH_NPCM) += nuvoton-npcm845-evb.dtb
> +dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-iot-512m.dtb
> +dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-som-256m.dtb
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
> new file mode 100644
> index 000000000000..dffcaef1e6d8
> --- /dev/null
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
> @@ -0,0 +1,24 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Shan-Chun Hung <[email protected]>
> + * Jacky huang <[email protected]>
> + */
> +
> +/dts-v1/;
> +#include "ma35d1.dtsi"
> +
> +/ {
> + model = "Nuvoton MA35D1-IoT";
> + compatible = "nuvoton,ma35d1-iot", "nuvoton,ma35d1";
> +
> + chosen {
> + stdout-path = "serial0:115200n8";
> + };
> +
> + mem: memory@80000000 {
> + device_type = "memory";
> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
> + };
> +};
> +
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> new file mode 100644
> index 000000000000..3e6c3d5469ac
> --- /dev/null
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
> @@ -0,0 +1,23 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Shan-Chun Hung <[email protected]>
> + * Jacky huang <[email protected]>
> + */
> +
> +/dts-v1/;
> +#include "ma35d1.dtsi"
> +
> +/ {
> + model = "Nuvoton MA35D1-SOM";
> + compatible = "nuvoton,ma35d1-som", "nuvoton,ma35d1";
> +
> + chosen {
> + stdout-path = "serial0:115200n8";
> + };
> +
> + mem: memory@80000000 {
> + device_type = "memory";
> + reg = <0x00000000 0x80000000 0 0x10000000>; /* 256M DRAM */
> + };
> +};
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> new file mode 100644
> index 000000000000..8c855f6b330a
> --- /dev/null
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> @@ -0,0 +1,272 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Shan-Chun Hung <[email protected]>
> + * Jacky huang <[email protected]>
> + */
> +
> +#include <dt-bindings/interrupt-controller/arm-gic.h>
> +#include <dt-bindings/input/input.h>
> +#include <dt-bindings/gpio/gpio.h>
> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> +#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
> +
> +/ {
> + compatible = "nuvoton,ma35d1";
> + interrupt-parent = <&gic>;
> + #address-cells = <2>;
> + #size-cells = <2>;
> +
> + aliases {
> + serial0 = &uart0;
> + serial1 = &uart1;
> + serial2 = &uart2;
> + serial3 = &uart3;
> + serial4 = &uart4;
> + serial5 = &uart5;
> + serial6 = &uart6;
> + serial7 = &uart7;
> + serial8 = &uart8;
> + serial9 = &uart9;
> + serial10 = &uart10;
> + serial11 = &uart11;
> + serial12 = &uart12;
> + serial13 = &uart13;
> + serial14 = &uart14;
> + serial15 = &uart15;
> + serial16 = &uart16;
Aliases of interfaces coming out of SoC are properties of boards, not
SoC DTSI.
> + };
> +
> + chosen {
> + stdout-path = "serial0:115200n8";
> + };
> +
> + cpus {
> + #address-cells = <2>;
> + #size-cells = <0>;
Blank line.
> + cpu0: cpu@0 {
> + device_type = "cpu";
> + compatible = "arm,cortex-a35";
> + reg = <0x0 0x0>;
> + enable-method = "psci";
> + next-level-cache = <&L2_0>;
> + };
Between every node as well.
> + cpu1: cpu@1 {
> + device_type = "cpu";
> + compatible = "arm,cortex-a35";
> + reg = <0x0 0x1>;
> + enable-method = "psci";
> + next-level-cache = <&L2_0>;
> + };
> + L2_0: l2-cache0 {
> + compatible = "cache";
> + cache-level = <2>;
> + };
> + };
> +
> + psci {
> + compatible = "arm,psci-0.2";
> + method = "smc";
> + };
> +
> + clk_hxt: clock_hxt {
No underscores in node names.
> + compatible = "fixed-clock";
> + #clock-cells = <0>;
> + clock-frequency = <24000000>;
> + clock-output-names = "clk_hxt";
This looks like a property of boards, not SoC. Are you sure the clock
physically is in every SoC? If so, why it is not part of clock
controller? (before you start explaining what is this, have in mind that
I am pretty sure I know what is this, so rather answer the questions)
> + };
> +
> + timer {
> + compatible = "arm,armv8-timer";
> + interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) |
> + IRQ_TYPE_LEVEL_LOW)>, /* Physical Secure */
> + <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) |
> + IRQ_TYPE_LEVEL_LOW)>, /* Physical Non-Secure */
> + <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) |
> + IRQ_TYPE_LEVEL_LOW)>, /* Virtual */
> + <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) |
> + IRQ_TYPE_LEVEL_LOW)>; /* Hypervisor */
> + clock-frequency = <12000000>;
> + interrupt-parent = <&gic>;
> + };
> +
> + sys: system-management@40460000 {
> + compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
> + reg = <0x0 0x40460000 0x0 0x200>;
> +
> + reset: reset-controller {
> + compatible = "nuvoton,ma35d1-reset";
> + regmap = <&sys>;
> + #reset-cells = <1>;
> + };
> + };
> +
> + clk: clock-controller@40460200 {
> + compatible = "nuvoton,ma35d1-clk", "syscon";
> + reg = <0x00000000 0x40460200 0x0 0x100>;
> + #clock-cells = <1>;
> + clocks = <&clk_hxt>;
> + clock-names = "clk_hxt";
> + assigned-clocks = <&clk CAPLL>,
> + <&clk DDRPLL>,
> + <&clk APLL>,
> + <&clk EPLL>,
> + <&clk VPLL>;
> + assigned-clock-rates = <800000000>,
> + <266000000>,
> + <180000000>,
> + <500000000>,
> + <102000000>;
> + nuvoton,pll-mode = <0>, <1>, <0>, <0>, <0>;
> + nuvoton,sys = <&sys>;
> + };
> +
> + gic: interrupt-controller@50801000 {
> + compatible = "arm,gic-400";
> + #interrupt-cells = <3>;
> + interrupt-parent = <&gic>;
> + interrupt-controller;
> + reg = <0x0 0x50801000 0 0x1000>, /* GICD */
> + <0x0 0x50802000 0 0x2000>, /* GICC */
> + <0x0 0x50804000 0 0x2000>, /* GICH */
> + <0x0 0x50806000 0 0x2000>; /* GICV */
reg is second property.
> + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0x13) |
> + IRQ_TYPE_LEVEL_HIGH)>;
> + };
> +
> + uart0:serial@40700000 {
> + compatible = "nuvoton,ma35d1-uart";
> + reg = <0x0 0x40700000 0x0 0x100>;
> + interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk UART0_GATE>;
> + status = "okay";
Why? Drop the line... or convert it to disabled. Otherwise, why every
SoC has serial0 enabled? Is it used internally?
Best regards,
Krzysztof
On 15/03/2023 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> The clock controller generates clocks for the whole chip, including
> system clocks and all peripheral clocks. This driver support ma35d1
> clock gating, divider, and individual PLL configuration.
>
> There are 6 PLLs in ma35d1 SoC:
> - CA-PLL for the two Cortex-A35 CPU clock
> - SYS-PLL for system bus, which comes from the companion MCU
> and cannot be programmed by clock controller.
> - DDR-PLL for DDR
> - EPLL for GMAC and GFX, Display, and VDEC IPs.
> - VPLL for video output pixel clock
> - APLL for SDHC, I2S audio, and other IPs.
> CA-PLL has only one operation mode.
> DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
> operation modes: integer mode, fraction mode, and spread specturm mode.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> drivers/clk/Makefile | 1 +
> drivers/clk/nuvoton/Makefile | 4 +
> drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 ++++
> drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 +++++++++++++
> drivers/clk/nuvoton/clk-ma35d1.c | 970 +++++++++++++++++++++++
> drivers/clk/nuvoton/clk-ma35d1.h | 198 +++++
> 6 files changed, 1851 insertions(+)
> create mode 100644 drivers/clk/nuvoton/Makefile
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
>
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index e3ca0d058a25..2e7916d269e1 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -103,6 +103,7 @@ endif
> obj-y += mstar/
> obj-y += mvebu/
> obj-$(CONFIG_ARCH_MXS) += mxs/
> +obj-$(CONFIG_ARCH_NUVOTON) += nuvoton/
Missing compile test.
(...)
> +
> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
> new file mode 100644
> index 000000000000..faae5a17e425
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
> @@ -0,0 +1,198 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
> +#define __DRV_CLK_NUVOTON_MA35D1_H
> +
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/spinlock.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/ma35d1-sys.h>
> +
> +enum ma35d1_pll_type {
> + MA35D1_CAPLL,
> + MA35D1_DDRPLL,
> + MA35D1_APLL,
> + MA35D1_EPLL,
> + MA35D1_VPLL,
> +};
> +
> +enum ma35d1_pll_mode {
> + VSIPLL_INTEGER_MODE,
> + VSIPLL_FRACTIONAL_MODE,
> + VSIPLL_SS_MODE,
> +};
> +
> +/* VSI-PLL CTL0~2 */
> +#define VSIPLL_CTL0 0x0
> +#define VSIPLL_CTL1 0x4
> +#define VSIPLL_CTL2 0x8
> +
> +/* VSI-PLL Specification limits */
> +#define VSIPLL_FREF_MAX_FREQ 200000000UL
> +#define VSIPLL_FREF_MIN_FREQ 1000000UL
> +#define VSIPLL_FREFDIVM_MAX_FREQ 40000000UL
> +#define VSIPLL_FREFDIVM_MIN_FREQ0 1000000UL
> +#define VSIPLL_FREFDIVM_MIN_FREQ1 10000000UL
> +#define VSIPLL_FCLK_MAX_FREQ 2400000000UL
> +#define VSIPLL_FCLK_MIN_FREQ 600000000UL
> +#define VSIPLL_FCLKO_MAX_FREQ 2400000000UL
> +#define VSIPLL_FCLKO_MIN_FREQ 85700000UL
> +#define VSIPLL_SPREAD_RANGE 194
> +#define VSIPLL_MODULATION_FREQ 50000
> +
> +/* Clock Control Registers Offset */
> +#define REG_CLK_PWRCTL (0x00)
> +#define REG_CLK_SYSCLK0 (0x04)
> +#define REG_CLK_SYSCLK1 (0x08)
> +#define REG_CLK_APBCLK0 (0x0C)
> +#define REG_CLK_APBCLK1 (0x10)
> +#define REG_CLK_APBCLK2 (0x14)
> +#define REG_CLK_CLKSEL0 (0x18)
> +#define REG_CLK_CLKSEL1 (0x1C)
> +#define REG_CLK_CLKSEL2 (0x20)
> +#define REG_CLK_CLKSEL3 (0x24)
> +#define REG_CLK_CLKSEL4 (0x28)
> +#define REG_CLK_CLKDIV0 (0x2C)
> +#define REG_CLK_CLKDIV1 (0x30)
> +#define REG_CLK_CLKDIV2 (0x34)
> +#define REG_CLK_CLKDIV3 (0x38)
> +#define REG_CLK_CLKDIV4 (0x3C)
> +#define REG_CLK_CLKOCTL (0x40)
> +#define REG_CLK_STATUS (0x50)
> +#define REG_CLK_PLL0CTL0 (0x60)
> +#define REG_CLK_PLL2CTL0 (0x80)
> +#define REG_CLK_PLL2CTL1 (0x84)
> +#define REG_CLK_PLL2CTL2 (0x88)
> +#define REG_CLK_PLL3CTL0 (0x90)
> +#define REG_CLK_PLL3CTL1 (0x94)
> +#define REG_CLK_PLL3CTL2 (0x98)
> +#define REG_CLK_PLL4CTL0 (0xA0)
> +#define REG_CLK_PLL4CTL1 (0xA4)
> +#define REG_CLK_PLL4CTL2 (0xA8)
> +#define REG_CLK_PLL5CTL0 (0xB0)
> +#define REG_CLK_PLL5CTL1 (0xB4)
> +#define REG_CLK_PLL5CTL2 (0xB8)
> +#define REG_CLK_CLKDCTL (0xC0)
> +#define REG_CLK_CLKDSTS (0xC4)
> +#define REG_CLK_CDUPB (0xC8)
> +#define REG_CLK_CDLOWB (0xCC)
> +#define REG_CLK_CKFLTRCTL (0xD0)
> +#define REG_CLK_TESTCLK (0xF0)
> +#define REG_CLK_PLLCTL (0x40)
> +
> +/* Constant Definitions for Clock Controller */
> +#define SMICPLLCTL0_FBDIV_POS (0)
> +#define SMICPLLCTL0_FBDIV_MSK (0xfful << SMICPLLCTL0_FBDIV_POS)
> +#define SMICPLLCTL0_INDIV_POS (8)
> +#define SMICPLLCTL0_INDIV_MSK (0xful << SMICPLLCTL0_INDIV_POS)
> +#define SMICPLLCTL0_OUTDIV_POS (12)
> +#define SMICPLLCTL0_OUTDIV_MSK (0x3ul << SMICPLLCTL0_OUTDIV_POS)
> +#define SMICPLLCTL0_PD_POS (16)
> +#define SMICPLLCTL0_PD_MSK (0x1ul << SMICPLLCTL0_PD_POS)
> +#define SMICPLLCTL0_BP_POS (17)
> +#define SMICPLLCTL0_BP_MSK (0x1ul << SMICPLLCTL0_BP_POS)
> +#define VSIPLLCTL0_FBDIV_POS (0)
> +#define VSIPLLCTL0_FBDIV_MSK (0x7fful << VSIPLLCTL0_FBDIV_POS)
> +#define VSIPLLCTL0_INDIV_POS (12)
> +#define VSIPLLCTL0_INDIV_MSK (0x3ful << VSIPLLCTL0_INDIV_POS)
> +#define VSIPLLCTL0_MODE_POS (18)
> +#define VSIPLLCTL0_MODE_MSK (0x3ul << VSIPLLCTL0_MODE_POS)
> +#define VSIPLLCTL0_SSRATE_POS (20)
> +#define VSIPLLCTL0_SSRATE_MSK (0x7fful << VSIPLLCTL0_SSRATE_POS)
> +#define VSIPLLCTL1_PD_POS (0)
> +#define VSIPLLCTL1_PD_MSK (0x1ul << VSIPLLCTL1_PD_POS)
> +#define VSIPLLCTL1_BP_POS (1)
> +#define VSIPLLCTL1_BP_MSK (0x1ul << VSIPLLCTL1_BP_POS)
> +#define VSIPLLCTL1_OUTDIV_POS (4)
> +#define VSIPLLCTL1_OUTDIV_MSK (0x7ul << VSIPLLCTL1_OUTDIV_POS)
> +#define VSIPLLCTL1_FRAC_POS (8)
> +#define VSIPLLCTL1_FRAC_MSK (0xfffffful << VSIPLLCTL1_FRAC_POS)
> +#define VSIPLLCTL2_SLOPE_POS (0)
> +#define VSIPLLCTL2_SLOPE_MSK (0xfffffful << VSIPLLCTL2_SLOPE_POS)
> +
> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type, u8 u8mode,
> + const char *name, const char *parent,
> + unsigned long targetFreq,
> + void __iomem *base,
> + struct regmap *regmap);
> +
> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev,
> + const char *name,
> + const char *parent_name,
> + unsigned long flags,
> + void __iomem *reg, u8 shift,
> + u8 width, u32 mask_bit);
> +
> +extern spinlock_t ma35d1_lock;
Why this is here?
> +
> +static inline struct clk_hw *ma35d1_clk_fixed(const char *name, int rate)
> +{
> + return clk_hw_register_fixed_rate(NULL, name, NULL, 0, rate);
> +}
> +
Why all these are here?
> +
> +#endif /* __DRV_CLK_NUVOTON_MA35D1_H */
Best regards,
Krzysztof
On 15/03/2023 08:29, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This driver supports individual IP reset for ma35d1. The reset
> control registers is a subset of system control registers.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> drivers/reset/Kconfig | 6 ++
> drivers/reset/Makefile | 1 +
> drivers/reset/reset-ma35d1.c | 152 +++++++++++++++++++++++++++++++++++
> 3 files changed, 159 insertions(+)
> create mode 100644 drivers/reset/reset-ma35d1.c
>
> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> index 2a52c990d4fe..47671060d259 100644
> --- a/drivers/reset/Kconfig
> +++ b/drivers/reset/Kconfig
> @@ -143,6 +143,12 @@ config RESET_NPCM
> This enables the reset controller driver for Nuvoton NPCM
> BMC SoCs.
>
> +config RESET_NUVOTON_MA35D1
> + bool "Nuvton MA35D1 Reset Driver"
> + default ARCH_NUVOTON
|| COMPILE_TEST
> + help
> + This enables the reset controller driver for Nuvoton MA35D1 SoC.
> +
Best regards,
Krzysztof
Dear Jiri,
Thanks for your review.
I will create #define constant to replace every occurrence of magics or
hard coding number.
For every occurrence of "struct uart_ma35d1_port *up = (struct
uart_ma35d1_port *)port; ",
it will be replaced with to_ma35d1_uart_port() .
On 2023/3/15 下午 06:13, Jiri Slaby wrote:
> On 15. 03. 23, 8:29, Jacky Huang wrote:
>> --- /dev/null
>> +++ b/drivers/tty/serial/ma35d1_serial.c
>> @@ -0,0 +1,842 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * MA35D1 serial driver
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/moduleparam.h>
>
> What parameters does this module have?
+#include <linux/ioport.h>
>> +#include <linux/init.h>
>> +#include <linux/console.h>
>> +#include <linux/sysrq.h>
>> +#include <linux/delay.h>
>
> What do you use from delay.h?
>
>> +#include <linux/platform_device.h>
>> +#include <linux/tty.h>
>> +#include <linux/tty_flip.h>
>> +#include <linux/clk.h>
>> +#include <linux/serial_reg.h>
>> +#include <linux/serial_core.h>
>> +#include <linux/serial.h>
>> +#include <linux/nmi.h>
>
> nmi.h?
>
> Please clean up all of the includes.
>
moduleparam.h, delay.h, and nmi.h are not used.
I will test and remove all unused #include.
>> +#include <linux/mutex.h>
>> +#include <linux/slab.h>
>> +#include <linux/uaccess.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/io.h>
>> +#include <asm/irq.h>
>> +#include <asm/serial.h>
>> +#include "ma35d1_serial.h"
>> +
>> +#define UART_NR 17
>> +
>> +static struct uart_driver ma35d1serial_reg;
>> +struct clk *clk;
>> +
>> +struct uart_ma35d1_port {
>> + struct uart_port port;
>> + u16 capabilities; /* port capabilities */
>> + u8 ier;
>> + u8 lcr;
>> + u8 mcr;
>> + u8 mcr_mask; /* mask of user bits */
>> + u8 mcr_force; /* mask of forced bits */
>
> Where are all those used?
>
I will remove the unused structure members in the next version.
>> + struct serial_rs485 rs485; /* rs485 settings */
>> + u32 baud_rate;
>
> And this one.
>
It's not used. I will remove it.
>> + int rx_count;
>> + u32 console_baud_rate;
>> + u32 console_line;
>> + u32 console_int;
>> +};
>> +
>> +static struct device_node *ma35d1serial_uart_nodes[UART_NR];
>> +static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
>
>> +static void __stop_tx(struct uart_ma35d1_port *p);
>
> What for?
This function is used to disable TX FIFO empty interrupt.
naming issue?
>
>> +static void transmit_chars(struct uart_ma35d1_port *up);
>> +
>> +static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port
>> *uart)
>> +{
>> + return container_of(uart, struct uart_ma35d1_port, port);
>> +}
>> +
>> +static u32 serial_in(struct uart_ma35d1_port *p, int offset)
>
> Q: int? A: No.
will modify "int offset" to "u32 offset"
>
>> +{
>> + return __raw_readl(p->port.membase + offset);
>> +}
>> +
>> +static void serial_out(struct uart_ma35d1_port *p, int offset, int
>> value)
>
> No ints here, please.
will modify offset and value data type to u32
>
>> +{
>> + __raw_writel(value, p->port.membase + offset);
>> +}
>> +
>> +static void __stop_tx(struct uart_ma35d1_port *p)
>> +{
>> + u32 ier;
>> +
>> + ier = serial_in(p, UART_REG_IER);
>> + if (ier & THRE_IEN)
>> + serial_out(p, UART_REG_IER, ier & ~THRE_IEN);
>> +}
>> +
>> +static void ma35d1serial_stop_tx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>
> Despite you have to_ma35d1_uart_port(), you do this?
>
You're right. I will use to_ma35d1_uart_port() to replace it.
>> +
>> + __stop_tx(up);
>> +}
>> +
>> +static void ma35d1serial_start_tx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 ier;
>> + struct circ_buf *xmit = &up->port.state->xmit;
>> +
>> + ier = serial_in(up, UART_REG_IER);
>> + serial_out(up, UART_REG_IER, ier & ~THRE_IEN);
>> + if (uart_circ_chars_pending(xmit) <
>> + (16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0x3F)))
>
> You look like you need a helper for this computation (hint:
> GENMASK()). What do those magic constants mean?
>
I will add #define macro for the bit mask..
>> + transmit_chars(up);
>> + serial_out(up, UART_REG_IER, ier | THRE_IEN);
>> +}
>> +
>> +static void ma35d1serial_stop_rx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>
> Bah. Nah.
>> +
>> + serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) &
>> ~RDA_IEN);
>> +}
>> +
>> +static void
>> +receive_chars(struct uart_ma35d1_port *up)
>> +{
>> + u8 ch;
>> + u32 fsr;
>> + u32 isr;
>> + u32 dcnt;
>> + char flag;
>
> flag is u8 too. And a reverse xmas tree, please. Actually, you can put
> all those u32 to a single line.
>
OK, I will fix it.
>> +
>> + isr = serial_in(up, UART_REG_ISR);
>> + fsr = serial_in(up, UART_REG_FSR);
>> +
>> + while (!(fsr & RX_EMPTY)) {
>> + flag = TTY_NORMAL;
>> + up->port.icount.rx++;
>> +
>> + if (unlikely(fsr & (BIF | FEF | PEF | RX_OVER_IF))) {
>> + if (fsr & BIF) {
>> + serial_out(up, UART_REG_FSR, BIF);
>> + up->port.icount.brk++;
>> + if (uart_handle_break(&up->port))
>> + continue;
>> + }
>> + if (fsr & FEF) {
>> + serial_out(up, UART_REG_FSR, FEF);
>> + up->port.icount.frame++;
>> + }
>> + if (fsr & PEF) {
>> + serial_out(up, UART_REG_FSR, PEF);
>> + up->port.icount.parity++;
>> + }
>> + if (fsr & RX_OVER_IF) {
>> + serial_out(up, UART_REG_FSR, RX_OVER_IF);
>> + up->port.icount.overrun++;
>> + }
>> + if (fsr & BIF)
>> + flag = TTY_BREAK;
>> + if (fsr & PEF)
>> + flag = TTY_PARITY;
>> + if (fsr & FEF)
>> + flag = TTY_FRAME;
>> + }
>> + ch = (u8)serial_in(up, UART_REG_RBR);
>> + if (uart_handle_sysrq_char(&up->port, ch))
>> + continue;
>> +
>> + uart_insert_char(&up->port, fsr, RX_OVER_IF, ch, flag);
>
> No lock needed?
>
I will wrap it with spin_lock and spin_unlock.
>> + up->rx_count++;
>> + dcnt = (serial_in(up, UART_REG_FSR) >> 8) & 0x3f;
>
> More magic constants. No.
>
>> + if (up->rx_count > 1023) {
>> + spin_lock(&up->port.lock);
>> + tty_flip_buffer_push(&up->port.state->port);
>> + spin_unlock(&up->port.lock);
>> + up->rx_count = 0;
>> + if ((isr & RXTO_IF) && (dcnt == 0))
>> + goto tout_end;
>> + }
>> + if (isr & RDA_IF) {
>> + if (dcnt == 1)
>> + return;
>> + }
>> + fsr = serial_in(up, UART_REG_FSR);
>> + }
>> + spin_lock(&up->port.lock);
>> + tty_flip_buffer_push(&up->port.state->port);
>> + spin_unlock(&up->port.lock);
>> +tout_end:
>> + up->rx_count = 0;
>> +}
>> +
>> +static void transmit_chars(struct uart_ma35d1_port *up)
>
> Why this cannot use uart_port_tx()?
OK, I will rename it as uart_port_tx().
>
>> +{
>> + struct circ_buf *xmit = &up->port.state->xmit;
>> + int count = 16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0xF);
>> +
>> + if (serial_in(up, UART_REG_FSR) & TX_FULL)
>> + count = 0;
>> + if (up->port.x_char) {
>> + serial_out(up, UART_REG_THR, up->port.x_char);
>> + up->port.icount.tx++;
>> + up->port.x_char = 0;
>> + return;
>> + }
>> + if (uart_tx_stopped(&up->port)) {
>> + ma35d1serial_stop_tx(&up->port);
>> + return;
>> + }
>> + if (uart_circ_empty(xmit)) {
>> + __stop_tx(up);
>> + return;
>> + }
>> + while (count > 0) {
>> + serial_out(up, UART_REG_THR, xmit->buf[xmit->tail]);
>> + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
>> + up->port.icount.tx++;
>> + count--;
>> + if (uart_circ_empty(xmit))
>> + break;
>> + }
>> + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
>> + uart_write_wakeup(&up->port);
>> + if (uart_circ_empty(xmit))
>> + __stop_tx(up);
>> +}
>> +
>> +static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)dev_id;
>> + u32 isr, fsr;
>> +
>> + isr = serial_in(up, UART_REG_ISR);
>> + fsr = serial_in(up, UART_REG_FSR);
>> + if (isr & (RDA_IF | RXTO_IF))
>> + receive_chars(up);
>> + if (isr & THRE_INT)
>> + transmit_chars(up);
>> + if (fsr & (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF))
>> + serial_out(up, UART_REG_FSR,
>> + (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF));
>> +
>> + return IRQ_HANDLED;
>
> You give no way for OS to disable the irq when the HW goes crazy. I.e.
> you should return IRQ_HANDLED only when you really handled the irq.
>
>> +}
> ...
>> +static u32 ma35d1serial_get_mctrl(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 status;
>> + u32 ret = 0;
>> +
>> + status = serial_in(up, UART_REG_MSR);
>> + if (!(status & 0x10))
>
> 0x10 is magic.
>
>> + ret |= TIOCM_CTS;
>> + return ret;
>> +}
>> +
>> +static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 mcr = 0;
>> + u32 ier = 0;
>> +
>> + if (mctrl & TIOCM_RTS) {
>> + /* set RTS high level trigger */
>> + mcr = serial_in(up, UART_REG_MCR);
>> + mcr |= 0x200;
>> + mcr &= ~(0x2);
>> + }
>> + if (up->mcr & UART_MCR_AFE) {
>> + /* set RTS high level trigger */
>> + mcr = serial_in(up, UART_REG_MCR);
>> + mcr |= 0x200;
>> + mcr &= ~(0x2);
>
> This is repeated. Parentheses are superfluous. And again, 0x200, 0x2
> are magic.
>
>> +
>> + /* enable CTS/RTS auto-flow control */
>> + serial_out(up, UART_REG_IER,
>> + (serial_in(up, UART_REG_IER) | (0x3000)));
>> +
>> + /* Set hardware flow control */
>> + up->port.flags |= UPF_HARD_FLOW;
>> + } else {
>> + /* disable CTS/RTS auto-flow control */
>> + ier = serial_in(up, UART_REG_IER);
>> + ier &= ~(0x3000);
>
> Detto.
>
>> + serial_out(up, UART_REG_IER, ier);
>> +
>> + /* un-set hardware flow control */
>> + up->port.flags &= ~UPF_HARD_FLOW;
>> + }
>> +
>> + /* set CTS high level trigger */
>> + serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) |
>> (0x100)));
>> + serial_out(up, UART_REG_MCR, mcr);
>> +}
> ...
>> +static int ma35d1serial_startup(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + struct tty_struct *tty = port->state->port.tty;
>> + int retval;
>> +
>> + /* Reset FIFO */
>> + serial_out(up, UART_REG_FCR, TFR | RFR /* | RX_DIS */);
>
> So why not RX_DIS?
>
>> +
>> + /* Clear pending interrupts */
>> + serial_out(up, UART_REG_ISR, 0xFFFFFFFF);
>> +
>> + retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
>> + tty ? tty->name : "ma35d1_serial", port);
>> + if (retval) {
>> + dev_err(up->port.dev, "request irq failed.\n");
>> + return retval;
>> + }
>> +
>> + /* Now, initialize the UART */
>> + /* FIFO trigger level 4 byte */
>> + /* RTS trigger level 8 bytes */
>> + serial_out(up, UART_REG_FCR,
>> + serial_in(up, UART_REG_FCR) | 0x10 | 0x20000);
>> + serial_out(up, UART_REG_LCR, 0x7); /* 8 bit */
>> + serial_out(up, UART_REG_TOR, 0x40);
>
> You know what.
>
>> + serial_out(up, UART_REG_IER,
>> + RTO_IEN | RDA_IEN | TIME_OUT_EN | BUFERR_IEN);
>> + return 0;
>> +}
>> +
>> +static void ma35d1serial_shutdown(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + free_irq(port->irq, port);
>> +
>> + /* Disable interrupts from this port */
>> + serial_out(up, UART_REG_IER, 0);
>
> The two lines are switched, IMO. First disable HW, then let the ISR
> finish and free it.
You're right. I will fix it.
>
>> +}
>> +
>> +static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
>> +{
>> + u32 quot;
>> +
>> + quot = (port->uartclk / baud) - 2;
>> + return quot;
>
> quot variable is completely superfluous.
remove quot
and
return (port->uartclk / baud) - 2;
>
>> +}
>> +
>> +static void ma35d1serial_set_termios(struct uart_port *port,
>> + struct ktermios *termios,
>> + const struct ktermios *old)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 lcr = 0;
>> + unsigned long flags;
>> + u32 baud, quot;
>> +
>> + switch (termios->c_cflag & CSIZE) {
>> + case CS5:
>> + lcr = 0;
>> + break;
>> + case CS6:
>> + lcr |= 1;
>> + break;
>> + case CS7:
>> + lcr |= 2;
>> + break;
>> + case CS8:
>> + default:
>> + lcr |= 3;
>> + break;
>> + }
>
> IOW:
> lcr = UART_LCR_WLEN(tty_get_char_size(termios->c_cflag));
>
All registers in this uart controller are 32-bits.
lcr will finally be written to the register UART_REG_LCR.of
which bit[1:0] represents for UART word length.
00 - 5 bits
01 - 6 bits
10 - 7 bits
11 - 8bits
Instead, I will add
#define WLS_5 0x0
#define WLS_6 0x1
#define WLS_7 0x2
#define WLS_8 0x3
and replace lcr |= 1 with
lcr |= WLS_6;
>> +
>> + if (termios->c_cflag & CSTOPB)
>> + lcr |= NSB;
>> + if (termios->c_cflag & PARENB)
>> + lcr |= PBE;
>> + if (!(termios->c_cflag & PARODD))
>> + lcr |= EPE;
>> + if (termios->c_cflag & CMSPAR)
>> + lcr |= SPE;
>> +
>> + baud = uart_get_baud_rate(port, termios, old, port->uartclk /
>> 0xffff,
>> + port->uartclk / 11);
>> +
>> + quot = ma35d1serial_get_divisor(port, baud);
>> +
>> + /*
>> + * Ok, we're now changing the port state. Do it with
>> + * interrupts disabled.
>> + */
>> + spin_lock_irqsave(&up->port.lock, flags);
>> +
>> + up->port.read_status_mask = RX_OVER_IF;
>> + if (termios->c_iflag & INPCK)
>> + up->port.read_status_mask |= FEF | PEF;
>> + if (termios->c_iflag & (BRKINT | PARMRK))
>> + up->port.read_status_mask |= BIF;
>> +
>> + /*
>> + * Characteres to ignore
>> + */
>> + up->port.ignore_status_mask = 0;
>> + if (termios->c_iflag & IGNPAR)
>> + up->port.ignore_status_mask |= FEF | PEF;
>> + if (termios->c_iflag & IGNBRK) {
>> + up->port.ignore_status_mask |= BIF;
>> + /*
>> + * If we're ignoring parity and break indicators,
>> + * ignore overruns too (for real raw support).
>> + */
>> + if (termios->c_iflag & IGNPAR)
>> + up->port.ignore_status_mask |= RX_OVER_IF;
>> + }
>> + if (termios->c_cflag & CRTSCTS)
>> + up->mcr |= UART_MCR_AFE;
>> + else
>> + up->mcr &= ~UART_MCR_AFE;
>> +
>> + ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
>> + serial_out(up, UART_REG_BAUD, quot | 0x30000000);
>> + serial_out(up, UART_REG_LCR, lcr);
>> + spin_unlock_irqrestore(&up->port.lock, flags);
>> +}
> ...
>> +static void ma35d1serial_config_port(struct uart_port *port, int flags)
>> +{
>> + int ret;
>> +
>> + /*
>> + * Find the region that we can probe for. This in turn
>> + * tells us whether we can probe for the type of port.
>> + */
>> + ret = ma35d1serial_request_port(port);
>> + if (ret < 0)
>> + return;
>
> ma35d1serial_request_port() does nothing. You can remove it altogether.
>
OK, I will remove it.
>> + port->type = PORT_MA35D1;
>> +}
>
>
>> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd,
>> unsigned long arg)
>> +{
>> + switch (cmd) {
>> + default:
>> + return -ENOIOCTLCMD;
>> + }
>> + return 0;
>> +}
>
> Drop that completely.
>
OK.
>> +static void
>> +ma35d1serial_console_init_port(void)
>> +{
>> + int i = 0;
>> + struct device_node *np;
>> +
>> + for_each_matching_node(np, ma35d1_serial_of_match) {
>> + if (ma35d1serial_uart_nodes[i] == NULL) {
>> + ma35d1serial_uart_nodes[i] = np;
>> + i++;
>
> Unless the dt is broken, this is OK. But I would add a sanity check to i.
>
I will add
if (i == UART_NR)
break;
>> + }
>> + }
>> +}
> ...
>> +/*
>> + * Register a set of serial devices attached to a platform device.
>> + * The list is terminated with a zero flags entry, which means we
>> expect
>> + * all entries to have at least UPF_BOOT_AUTOCONF set.
>> + */
>> +static int ma35d1serial_probe(struct platform_device *pdev)
>> +{
>> + struct resource *res_mem;
>> + struct uart_ma35d1_port *up;
>> + int ret;
>> + struct clk *clk;
>> + int err;
>> +
>> + if (pdev->dev.of_node) {
>> + ret = of_alias_get_id(pdev->dev.of_node, "serial");
>> + if (ret < 0) {
>> + dev_err(&pdev->dev,
>> + "failed to get alias/pdev id, errno %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> + up = &ma35d1serial_ports[ret];
>> + up->port.line = ret;
>> + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!res_mem)
>> + return -ENODEV;
>> +
>> + up->port.iobase = res_mem->start;
>> + up->port.membase = ioremap(up->port.iobase, 0x10000);
>> + up->port.ops = &ma35d1serial_ops;
>> +
>> + spin_lock_init(&up->port.lock);
>> +
>> + clk = of_clk_get(pdev->dev.of_node, 0);
>> + if (IS_ERR(clk)) {
>> + err = PTR_ERR(clk);
>> + dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
>> + return -ENOENT;
>> + }
>> + err = clk_prepare_enable(clk);
>> + if (err)
>> + return -ENOENT;
>> +
>> + if (up->port.line != 0)
>> + up->port.uartclk = clk_get_rate(clk);
>> + up->port.irq = platform_get_irq(pdev, 0);
>> + up->port.dev = &pdev->dev;
>> + up->port.flags = UPF_BOOT_AUTOCONF;
>> + up->port.rs485_config = ma35d1serial_config_rs485;
>> + ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
>
> What if this fails?
>
I will add return value check.
>> + platform_set_drvdata(pdev, up);
>> + return 0;
>> +}
>> +
>> +/*
>> + * Remove serial ports registered against a platform device.
>> + */
>> +static int ma35d1serial_remove(struct platform_device *dev)
>> +{
>> + int i;
>> + struct uart_port *port = platform_get_drvdata(dev);
>> +
>> + free_irq(port->irq, port);
>
> Hmm, this doesn't look right. You did that already, or?
>
>> + for (i = 0; i < UART_NR; i++) {
>> + struct uart_ma35d1_port *up = &ma35d1serial_ports[i];
>> +
>> + if (up->port.dev == &dev->dev)
>
> You did platform_set_drvdata(), so why all this?
>
static int ma35d1serial_remove(struct platform_device *dev)
{
struct uart_port *port = platform_get_drvdata(dev);
uart_remove_one_port(&ma35d1serial_reg, &up->port);
free_irq(port->irq, port);
return 0;
}
>> + uart_remove_one_port(&ma35d1serial_reg, &up->port);
>> + }
>> + return 0;
>> +}
>
Best Regards,
Jacky Huang
On Wed, 15 Mar 2023, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> The system manager is a set of registers used for power control,
> multi-function pin control, USB phy control, IP reset, and other
> miscellaneous controls. It also contains some registers that
> provide SoC information and status.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> include/linux/mfd/ma35d1-sys.h | 95 ++++++++++++++++++++++++++++++++++
> 1 file changed, 95 insertions(+)
> create mode 100644 include/linux/mfd/ma35d1-sys.h
>
> diff --git a/include/linux/mfd/ma35d1-sys.h b/include/linux/mfd/ma35d1-sys.h
> new file mode 100644
> index 000000000000..dcd85231125d
> --- /dev/null
> +++ b/include/linux/mfd/ma35d1-sys.h
> @@ -0,0 +1,95 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2023 Nuvoton Technologies.
> + * Author: Chi-Fen Li <[email protected]>
> + *
> + * System management control registers of MA35D1 SoC
> + */
> +#ifndef __LINUX_MFD_MA35D1_SYS_H
> +#define __LINUX_MFD_MA35D1_SYS_H
> +
> +#define REG_SYS_PDID (0x000) /* Product and Device Identifier */
> +#define REG_SYS_PWRONOTP (0x004) /* Power-on Setting OTP Source */
> +#define REG_SYS_PWRONPIN (0x008) /* Power-on Setting Pin Source */
> +#define REG_SYS_RSTSTS (0x010) /* Reset Source Active Status */
> +#define REG_SYS_MISCRFCR (0x014) /* Miscellaneous Reset Function */
> +#define REG_SYS_RSTDEBCTL (0x018) /* Reset Pin De-bounce Control */
> +#define REG_SYS_LVRDCR (0x01C) /* Low Voltage Reset & Detect */
> +#define REG_SYS_IPRST0 (0x020) /* Reset Control Register 0 */
> +#define REG_SYS_IPRST1 (0x024) /* Reset Control Register 1 */
> +#define REG_SYS_IPRST2 (0x028) /* Reset Control Register 2 */
> +#define REG_SYS_IPRST3 (0x02C) /* Reset Control Register 3 */
> +#define REG_SYS_PMUCR (0x030) /* Power Management Unit Control */
> +#define REG_SYS_DDRCQCSR (0x034) /* DDR Q Channel Control and Status */
> +#define REG_SYS_PMUIEN (0x038) /* PMU Interrupt Enable */
> +#define REG_SYS_PMUSTS (0x03C) /* PMU Status */
> +#define REG_SYS_CA35WRBADR1 (0x040) /* A35 Core 1 Warm-boot Address */
> +#define REG_SYS_CA35WRBPAR1 (0x044) /* A35 Core 1 Warm-boot Parameter */
> +#define REG_SYS_CA35WRBADR2 (0x048) /* A35 Core 2 Warm-boot Address */
> +#define REG_SYS_CA35WRBPAR2 (0x04C) /* A35 Core 2 Warm-boot Parameter */
> +#define REG_SYS_USBPMISCR (0x060) /* USB PHY Miscellaneous Control */
> +#define REG_SYS_USBP0PCR (0x064) /* USB Port 0 PHY Control */
> +#define REG_SYS_USBP1PCR (0x068) /* USB Port 1 PHY Control */
> +#define REG_SYS_MISCFCR0 (0x070) /* Miscellaneous Function Control 0 */
> +#define REG_SYS_MISCFCR1 (0x074) /* Miscellaneous Function Control 1 */
> +#define REG_SYS_MISCIER (0x078) /* Miscellaneous Interrupt Enable */
> +#define REG_SYS_MISCISR (0x07C) /* Miscellaneous Interrupt Status */
> +#define REG_SYS_GPA_MFPL (0x080) /* GPIOA Multi-Function Control LSB */
> +#define REG_SYS_GPA_MFPH (0x084) /* GPIOA Multi-Function Control MSB */
> +#define REG_SYS_GPB_MFPL (0x088) /* GPIOB Multi-Function Control LSB */
> +#define REG_SYS_GPB_MFPH (0x08C) /* GPIOB Multi-Function Control MSB */
> +#define REG_SYS_GPC_MFPL (0x090) /* GPIOC Multi-Function Control LSB */
> +#define REG_SYS_GPC_MFPH (0x094) /* GPIOC Multi-Function Control MSB */
> +#define REG_SYS_GPD_MFPL (0x098) /* GPIOD Multi-Function Control LSB */
> +#define REG_SYS_GPD_MFPH (0x09C) /* GPIOD Multi-Function Control MSB */
> +#define REG_SYS_GPE_MFPL (0x0A0) /* GPIOE Multi-Function Control LSB */
> +#define REG_SYS_GPE_MFPH (0x0A4) /* GPIOE Multi-Function Control MSB */
> +#define REG_SYS_GPF_MFPL (0x0A8) /* GPIOF Multi-Function Control LSB */
> +#define REG_SYS_GPF_MFPH (0x0AC) /* GPIOF Multi-Function Control MSB */
> +#define REG_SYS_GPG_MFPL (0x0B0) /* GPIOG Multi-Function Control LSB */
> +#define REG_SYS_GPG_MFPH (0x0B4) /* GPIOG Multi-Function Control MSB */
> +#define REG_SYS_GPH_MFPL (0x0B8) /* GPIOH Multi-Function Control LSB */
> +#define REG_SYS_GPH_MFPH (0x0BC) /* GPIOH Multi-Function Control MSB */
> +#define REG_SYS_GPI_MFPL (0x0C0) /* GPIOI Multi-Function Control LSB */
> +#define REG_SYS_GPI_MFPH (0x0C4) /* GPIOI Multi-Function Control MSB */
> +#define REG_SYS_GPJ_MFPL (0x0C8) /* GPIOJ Multi-Function Control LSB */
> +#define REG_SYS_GPJ_MFPH (0x0CC) /* GPIOJ Multi-Function Control MSB */
> +#define REG_SYS_GPK_MFPL (0x0D0) /* GPIOK Multi-Function Control LSB */
> +#define REG_SYS_GPK_MFPH (0x0D4) /* GPIOK Multi-Function Control MSB */
> +#define REG_SYS_GPL_MFPL (0x0D8) /* GPIOL Multi-Function Control LSB */
> +#define REG_SYS_GPL_MFPH (0x0DC) /* GPIOL Multi-Function Control MSB */
> +#define REG_SYS_GPM_MFPL (0x0E0) /* GPIOM Multi-Function Control LSB */
> +#define REG_SYS_GPM_MFPH (0x0E4) /* GPIOM Multi-Function Control MSB */
> +#define REG_SYS_GPN_MFPL (0x0E8) /* GPION Multi-Function Control LSB */
> +#define REG_SYS_GPN_MFPH (0x0EC) /* GPION Multi-Function Control MSB */
> +#define REG_SYS_HIRCFTRIM (0x100) /* HIRC Frequency Trim Value */
> +#define REG_SYS_TSENSRFCR (0x104) /* Temperature Sensor Control */
> +#define REG_SYS_GMAC0MISCR (0x108) /* GMAC 0 Miscellaneous Control */
> +#define REG_SYS_GMAC1MISCR (0x10C) /* GMAC 1 Miscellaneous Control */
> +#define REG_SYS_MACAD0LSR (0x110) /* MAC Address 0 LSW */
> +#define REG_SYS_MACAD0HSR (0x114) /* MAC Address 0 HSW */
> +#define REG_SYS_MACAD1LSR (0x118) /* MAC Address 1 LSW */
> +#define REG_SYS_MACAD1HSR (0x11C) /* MAC Address 1 HSW */
> +#define REG_SYS_CSDBGCTL (0x120) /* CoreSight Debug Control */
> +#define REG_SYS_GPAB_MFOS (0x140) /* GPIOA/B Output Mode Select */
> +#define REG_SYS_GPCD_MFOS (0x144) /* GPIOC/D Output Mode Select */
> +#define REG_SYS_GPEF_MFOS (0x148) /* GPIOE/F Output Mode Select */
> +#define REG_SYS_GPGH_MFOS (0x14C) /* GPIOG/H Output Mode Select */
> +#define REG_SYS_GPIJ_MFOS (0x150) /* GPIOI/J Output Mode Select */
> +#define REG_SYS_GPKL_MFOS (0x154) /* GPIOK/L Output Mode Select */
> +#define REG_SYS_GPMN_MFOS (0x158) /* GPIOM/N Output Mode Select */
> +#define REG_SYS_UID0 (0x180) /* Unique Identifier Word 0 */
> +#define REG_SYS_UID1 (0x184) /* Unique Identifier Word 1 */
> +#define REG_SYS_UID2 (0x188) /* Unique Identifier Word 2 */
> +#define REG_SYS_UCID0 (0x190) /* Unique Customer Identifier 0 */
> +#define REG_SYS_UCID1 (0x194) /* Unique Customer Identifier 1 */
> +#define REG_SYS_UCID2 (0x198) /* Unique Customer Identifier 2 */
> +#define REG_SYS_RLKTZS (0x1A0) /* TZS Register Lock Control */
> +#define REG_SYS_RLKTZNS (0x1A4) /* TZNS Register Lock Control */
> +#define REG_SYS_RLKSUBM (0x1A8) /* SubM Register Lock Control */
> +#define REG_SYS_DPLPASWD (0x1B0) /* Deployed Password */
Remove the extra set of parenthesis from all those above. Hex numbers are
easier to read with lowercased letters so please convert them all to
lowercase.
--
i.
Dear Krzysztof,
On 2023/3/16 下午 03:31, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add the dt-bindings header for Nuvoton ma35d1, that gets shared
>> between the clock controller and clock references in the dts.
> I don't see the device binding. They come together.
I should move
Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
into this patch, right?
>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> .../dt-bindings/clock/nuvoton,ma35d1-clk.h | 253 ++++++++++++++++++
>> 1 file changed, 253 insertions(+)
>> create mode 100644 include/dt-bindings/clock/nuvoton,ma35d1-clk.h
>>
>> diff --git a/include/dt-bindings/clock/nuvoton,ma35d1-clk.h b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
>> new file mode 100644
>> index 000000000000..6c569fdd6e06
>> --- /dev/null
>> +++ b/include/dt-bindings/clock/nuvoton,ma35d1-clk.h
>> @@ -0,0 +1,253 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
> Dual license.
OK, I will fix it.
> Best regards,
> Krzysztof
>
On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This patchset adds initial support for the Nuvoton ma35d1 SoC, including
> initial device tree, clock driver, reset driver, and serial driver.
>
> This patchset cover letter is based from the initial support for Nuvoton
> ma35d1 to keep tracking the version history.
>
> This patchset had been applied to Linux kernel 6.3-rc2 and tested on the
> Nuvoton ma35d1 SOM evaluation board.
>
> (ma35d1 information:
> https://www.nuvoton.com/products/microprocessors/arm-cortex-a35-mpus/)
> MA35D1 porting on linux-5.10.y can be found at:
> https://github.com/OpenNuvoton/MPU-Family
Hi Jacky,
Thanks a lot for your submission. I saw this presented at
EmbeddedWorld yesterday and asked about mainline Linux
support, but did not expect to see the patches this soon ;-)
The easiest process for getting the series merged is to
have me add it the entire series to the SoC tree after the
individual drivers have been reviewed by the respective
subsystem maintainers that are already on Cc here. When
the review is complete, you can add [email protected] to Cc,
so they show up in patchwork, or alternatively send a pull
request for a git tree to that address. Until then, you
can add my own email address to Cc so I can follow the
reviews.
After the initial merge, the normal method for additional
device drivers is to have them sent for inclusion to the
subsystem maintainers. The soc tree and [email protected] address
is then only used for changes in arch/arm64, i.e. updates
to the dts files, Kconfig, defconfig and MAINTAINERS,
as well as the drivers/soc and drivers/firmware directories,
if you have anything in there.
If you have any additional questions about the process,
feel free to also ask me.
Arnd
On 16/03/2023 14:35, Jacky Huang wrote:
>
> Dear Krzysztof,
>
>
>
> On 2023/3/16 下午 03:31, Krzysztof Kozlowski wrote:
>> On 15/03/2023 08:28, Jacky Huang wrote:
>>> From: Jacky Huang <[email protected]>
>>>
>>> Add the dt-bindings header for Nuvoton ma35d1, that gets shared
>>> between the clock controller and clock references in the dts.
>> I don't see the device binding. They come together.
>
> I should move
> Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
> into this patch, right?
Yes.
Best regards,
Krzysztof
On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
> + mem: memory@80000000 {
> + device_type = "memory";
> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
> + };
> +};
In most machines, the memory size is detected by the boot loader
and filled in the dtb in memory before starting the kernel, so
you should not need two separate files here for the two common
memory configurations.
Since the machine is called 'som', I would assume that this is a
module that is integrated on another board, so more commonly one
would have a dtsi file for the som in addition to the one for the
soc, and have all the components of the module listed in this
file, while the dts file that includes the som.dtsi lists the
devices on the carrier board and enables the on-chip devices
that are connected to the outside.
Arnd
On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Enable basic drivers for ma35d1 booting up support: architecture,
> device tree, clock, reset, and uart.
>
> Signed-off-by: Jacky Huang <[email protected]>
The description does not seem to match the actual contents,
which only enable the platform but none of the drivers.
It's ok generally to enable options in the defconfig file
before you add the drivers, but if it's all part of a patch
series, I would probable more this bit to the end.
Arnd
On Thu, Mar 16, 2023, at 08:33, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add binding for ARMv8 based Nuvotn SoCs and platform boards.
>> Add initial bindings for ma35d1 series development boards.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> .../devicetree/bindings/arm/nuvoton.yaml | 30 +++++++++++++++++++
>
> And what is npcm for? Why it was made an directory?
>
> All these should be just one Nuvoton.
npcm is an unrelated product line, so I think it would be best
to rename the npcm directory to nuvoton and move the new
file in there, though I'm not sure about the name or what the
other chips are called.
My impression is that this one is more closely related to
the older Arm9 nuc900/w90x900/n9 chips that we dropped from
the kernel a while ago, while the npcm family has a different
origin.
Arnd
On Wed, Mar 15, 2023, at 08:29, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> Add entry for Nuvton ma35d1 maintainer and files
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> +F: Documentation/devicetree/bindings/*/*nuvoton*
> +F: arch/arm64/boot/dts/nuvoton/
This clashes with the existing entry for NPCM, so
contributors can easily get confused about where
to send their dts patches.
I don't have a good solution here, but maybe you can
discuss this with the npcm maintainers (added to Cc)
to see how they would like to handle this.
For me, the easiest way would be to have a single
maintainer send me all the patches for both ma35d1
and npcm, but that may not be practical for you.
> +F: drivers/*/*/*ma35d1*
> +F: drivers/*/*ma35d1*
> +F: include/dt-bindings/*/*ma35d1*
> +F: include/linux/mfd/ma35d1-sys.h
I would replace these with a single line
K: ma35d1
that should have the same effect.
Arnd
On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> The system manager is a set of registers used for power control,
> multi-function pin control, USB phy control, IP reset, and other
> miscellaneous controls. It also contains some registers that
> provide SoC information and status.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> include/linux/mfd/ma35d1-sys.h | 95 ++++++++++++++++++++++++++++++++++
> 1 file changed, 95 insertions(+)
> create mode 100644 include/linux/mfd/ma35d1-sys.h
>
> diff --git a/include/linux/mfd/ma35d1-sys.h b/include/linux/mfd/ma35d1-sys.h
> new file mode 100644
> index 000000000000..dcd85231125d
> --- /dev/null
> +++ b/include/linux/mfd/ma35d1-sys.h
> +
> +#define REG_SYS_PDID (0x000) /* Product and Device Identifier */
> +#define REG_SYS_PWRONOTP (0x004) /* Power-on Setting OTP Source */
> +#define REG_SYS_PWRONPIN (0x008) /* Power-on Setting Pin Source */
> +#define REG_SYS_RSTSTS (0x010) /* Reset Source Active Status */
...
It is a bit odd to have a header file in include/linux/mfd/
but only have the register numbers in there, and not an
actual drivers/mfd/ driver to go along with them.
I think what we often do is to just list the individual register
numbers in the drivers that need them and not have the central
header at all. On the other hand, I can see it's useful to
have this documented in one place, and we clearly don't want
to add a driver if none is needed.
Maybe Lee has a suggestion for how he'd like to handle this.
> +void ma35d1_reg_lock(void);
> +void ma35d1_reg_unlock(void);
These look like they were left over from an earlier version
of the code. Since you use the regmap framework, I think this
will take care of the locking for you.
Arnd
Hi,
I'll not note all things below because others have already seemingly
commented many things.
On Wed, 15 Mar 2023, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This adds UART and console driver for Nuvoton ma35d1 Soc.
>
> MA35D1 SoC provides up to 17 UART controllers, each with one uart port.
> The ma35d1 uart controller is not compatible with 8250.
> The uart controller supports:
> - Full-duplex asynchronous communications
> - Separates tx and tx 32/32 bytes entry FIFO for data payloads
> - Hardware auto-flow control
> - Programmable rx buffer trigger level (1/4/8/14/30 bytes)
> - Individual programmable baud rate generator for each channel
> - Supports nCTS, incoming data, rx FIFO reached threshold and
> RS-485 Address Match (AAD mode) wake-up function
> - Supports 8-bit rx buffer time-out detection function
> - Programmable tx data delay time
> - Supports Auto-Baud Rate measurement and baud rate compensation
> - Supports break error, frame error, parity error and rx/tx buffer
> overflow detection function
> – Programmable number of data bit, 5-, 6-, 7-, 8- bit character
> – Programmable parity bit, even, odd, no parity or stick parity bit
> generation and detection
> – Programmable stop bit, 1, 1.5, or 2 stop bit generation
> - Supports IrDA SIR function mode
> - Supports RS-485 function mode
> – Supports RS-485 9-bit mode
> – Supports hardware or software enables to program nRTS pin to control
> RS-485 transmission direction
> - Supports PDMA transfer function
> - Support Single-wire function mode.
This list is probably copy-pasted from somewhere but it doesn't match what
you implemented in the driver.
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> drivers/tty/serial/Kconfig | 18 +
> drivers/tty/serial/Makefile | 1 +
> drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++++++++++++++++
> drivers/tty/serial/ma35d1_serial.h | 93 ++++
> include/uapi/linux/serial_core.h | 3 +
> 5 files changed, 957 insertions(+)
> create mode 100644 drivers/tty/serial/ma35d1_serial.c
> create mode 100644 drivers/tty/serial/ma35d1_serial.h
>
> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
> index 625358f44419..cb47fe804595 100644
> --- a/drivers/tty/serial/Kconfig
> +++ b/drivers/tty/serial/Kconfig
> @@ -1562,6 +1562,24 @@ config SERIAL_SUNPLUS_CONSOLE
> you can alter that using a kernel command line option such as
> "console=ttySUPx".
>
> +config SERIAL_NUVOTON_MA35D1
> + tristate "Nuvoton MA35D1 family UART support"
> + depends on ARCH_NUVOTON || COMPILE_TEST
> + select SERIAL_CORE
> + help
> + This driver supports Nuvoton MA35D1 family UART ports. If you would
> + like to use them, you must answer Y or M to this option. Note that
> + for use as console, it must be included in kernel and not as a
> + module
> +
> +config SERIAL_NUVOTON_MA35D1_CONSOLE
> + bool "Console on a Nuvotn MA35D1 family UART port"
> + depends on SERIAL_NUVOTON_MA35D1=y
> + select SERIAL_CORE_CONSOLE
> + help
> + Select this options if you'd like to use the UART port0 of the
> + Nuvoton MA35D1 family as a console.
> +
> endmenu
>
> config SERIAL_MCTRL_GPIO
> diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
> index cd9afd9e3018..71ebeba06ff2 100644
> --- a/drivers/tty/serial/Makefile
> +++ b/drivers/tty/serial/Makefile
> @@ -93,3 +93,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
>
> obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
> obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
> +obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
> diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c
> new file mode 100644
> index 000000000000..8940d07c3777
> --- /dev/null
> +++ b/drivers/tty/serial/ma35d1_serial.c
> @@ -0,0 +1,842 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * MA35D1 serial driver
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/ioport.h>
> +#include <linux/init.h>
> +#include <linux/console.h>
> +#include <linux/sysrq.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/tty.h>
> +#include <linux/tty_flip.h>
> +#include <linux/clk.h>
> +#include <linux/serial_reg.h>
> +#include <linux/serial_core.h>
> +#include <linux/serial.h>
> +#include <linux/nmi.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <linux/io.h>
> +#include <asm/irq.h>
> +#include <asm/serial.h>
> +#include "ma35d1_serial.h"
> +
> +#define UART_NR 17
> +
> +static struct uart_driver ma35d1serial_reg;
> +struct clk *clk;
> +
> +struct uart_ma35d1_port {
> + struct uart_port port;
> + u16 capabilities; /* port capabilities */
> + u8 ier;
> + u8 lcr;
> + u8 mcr;
> + u8 mcr_mask; /* mask of user bits */
> + u8 mcr_force; /* mask of forced bits */
> + struct serial_rs485 rs485; /* rs485 settings */
> + u32 baud_rate;
> + int rx_count;
> + u32 console_baud_rate;
> + u32 console_line;
> + u32 console_int;
> +};
> +
> +static struct device_node *ma35d1serial_uart_nodes[UART_NR];
> +static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
> +static void __stop_tx(struct uart_ma35d1_port *p);
> +static void transmit_chars(struct uart_ma35d1_port *up);
Try to rearrange such that forward declarations are not necessary for
functions.
> +static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port *uart)
static inline
> +{
> + return container_of(uart, struct uart_ma35d1_port, port);
> +}
> +
> +static u32 serial_in(struct uart_ma35d1_port *p, int offset)
> +{
> + return __raw_readl(p->port.membase + offset);
> +}
> +
> +static void serial_out(struct uart_ma35d1_port *p, int offset, int value)
> +{
> + __raw_writel(value, p->port.membase + offset);
> +}
> +
> +static void __stop_tx(struct uart_ma35d1_port *p)
> +{
> + u32 ier;
> +
> + ier = serial_in(p, UART_REG_IER);
> + if (ier & THRE_IEN)
> + serial_out(p, UART_REG_IER, ier & ~THRE_IEN);
> +}
> +
> +static void ma35d1serial_stop_tx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + __stop_tx(up);
> +}
> +
> +static void ma35d1serial_start_tx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 ier;
> + struct circ_buf *xmit = &up->port.state->xmit;
> +
> + ier = serial_in(up, UART_REG_IER);
> + serial_out(up, UART_REG_IER, ier & ~THRE_IEN);
> + if (uart_circ_chars_pending(xmit) <
> + (16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0x3F)))
> + transmit_chars(up);
> + serial_out(up, UART_REG_IER, ier | THRE_IEN);
> +}
> +
> +static void ma35d1serial_stop_rx(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) & ~RDA_IEN);
> +}
> +
> +static void
> +receive_chars(struct uart_ma35d1_port *up)
> +{
> + u8 ch;
> + u32 fsr;
> + u32 isr;
> + u32 dcnt;
> + char flag;
> +
> + isr = serial_in(up, UART_REG_ISR);
> + fsr = serial_in(up, UART_REG_FSR);
> +
> + while (!(fsr & RX_EMPTY)) {
> + flag = TTY_NORMAL;
> + up->port.icount.rx++;
> +
> + if (unlikely(fsr & (BIF | FEF | PEF | RX_OVER_IF))) {
> + if (fsr & BIF) {
> + serial_out(up, UART_REG_FSR, BIF);
> + up->port.icount.brk++;
> + if (uart_handle_break(&up->port))
> + continue;
> + }
> + if (fsr & FEF) {
> + serial_out(up, UART_REG_FSR, FEF);
> + up->port.icount.frame++;
> + }
> + if (fsr & PEF) {
> + serial_out(up, UART_REG_FSR, PEF);
> + up->port.icount.parity++;
> + }
> + if (fsr & RX_OVER_IF) {
> + serial_out(up, UART_REG_FSR, RX_OVER_IF);
> + up->port.icount.overrun++;
> + }
Do you need to write each of those individually to clear(?) them or could
you just do one write here after the accounting is done:
serial_out(up, UART_REG_FSR, fsr & (BIF|FEF|PEF|RX_OVER_IF));
?
Also, add some driver specific prefix for the flag naming (all driver
specific ones, not just these if you have others besides these).
> + if (fsr & BIF)
> + flag = TTY_BREAK;
> + if (fsr & PEF)
> + flag = TTY_PARITY;
> + if (fsr & FEF)
> + flag = TTY_FRAME;
Are you sure this is the right prioritization or do you perhaps want else
ifs like in some other serial drivers?
> + }
> + ch = (u8)serial_in(up, UART_REG_RBR);
Drop the case.
> + if (uart_handle_sysrq_char(&up->port, ch))
> + continue;
> +
> + uart_insert_char(&up->port, fsr, RX_OVER_IF, ch, flag);
> + up->rx_count++;
> + dcnt = (serial_in(up, UART_REG_FSR) >> 8) & 0x3f;
> + if (up->rx_count > 1023) {
> + spin_lock(&up->port.lock);
> + tty_flip_buffer_push(&up->port.state->port);
> + spin_unlock(&up->port.lock);
> + up->rx_count = 0;
Why is all this ->rx_count trickery necessary? What's so special with the
size in question?
> + if ((isr & RXTO_IF) && (dcnt == 0))
> + goto tout_end;
> + }
> + if (isr & RDA_IF) {
> + if (dcnt == 1)
Merge to the same comdition.
dcnt could probably have a more descriptive name.
> + return;
> + }
> + fsr = serial_in(up, UART_REG_FSR);
> + }
> + spin_lock(&up->port.lock);
> + tty_flip_buffer_push(&up->port.state->port);
> + spin_unlock(&up->port.lock);
> +tout_end:
> + up->rx_count = 0;
> +}
> +
> +static void transmit_chars(struct uart_ma35d1_port *up)
> +{
> + struct circ_buf *xmit = &up->port.state->xmit;
> + int count = 16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0xF);
> +
> + if (serial_in(up, UART_REG_FSR) & TX_FULL)
> + count = 0;
> + if (up->port.x_char) {
> + serial_out(up, UART_REG_THR, up->port.x_char);
> + up->port.icount.tx++;
> + up->port.x_char = 0;
> + return;
> + }
> + if (uart_tx_stopped(&up->port)) {
> + ma35d1serial_stop_tx(&up->port);
> + return;
> + }
> + if (uart_circ_empty(xmit)) {
> + __stop_tx(up);
> + return;
> + }
> + while (count > 0) {
> + serial_out(up, UART_REG_THR, xmit->buf[xmit->tail]);
> + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
> + up->port.icount.tx++;
> + count--;
> + if (uart_circ_empty(xmit))
> + break;
> + }
> + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
> + uart_write_wakeup(&up->port);
> + if (uart_circ_empty(xmit))
> + __stop_tx(up);
> +}
> +
> +static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)dev_id;
> + u32 isr, fsr;
> +
> + isr = serial_in(up, UART_REG_ISR);
> + fsr = serial_in(up, UART_REG_FSR);
> + if (isr & (RDA_IF | RXTO_IF))
> + receive_chars(up);
> + if (isr & THRE_INT)
> + transmit_chars(up);
> + if (fsr & (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF))
> + serial_out(up, UART_REG_FSR,
> + (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF));
Hmm... Why write these again here... Didn't receive_chars() already
clear(?) most of these bits??
> +
> + return IRQ_HANDLED;
> +}
> +
> +static u32 ma35d1serial_tx_empty(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 fsr;
> +
> + fsr = serial_in(up, UART_REG_FSR);
> + return (fsr & (TE_FLAG | TX_EMPTY)) == (TE_FLAG | TX_EMPTY) ?
> + TIOCSER_TEMT : 0;
> +}
> +
> +static u32 ma35d1serial_get_mctrl(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 status;
> + u32 ret = 0;
> +
> + status = serial_in(up, UART_REG_MSR);
> + if (!(status & 0x10))
> + ret |= TIOCM_CTS;
> + return ret;
> +}
> +
> +static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 mcr = 0;
> + u32 ier = 0;
> +
> + if (mctrl & TIOCM_RTS) {
> + /* set RTS high level trigger */
> + mcr = serial_in(up, UART_REG_MCR);
> + mcr |= 0x200;
> + mcr &= ~(0x2);
> + }
> + if (up->mcr & UART_MCR_AFE) {
> + /* set RTS high level trigger */
> + mcr = serial_in(up, UART_REG_MCR);
> + mcr |= 0x200;
> + mcr &= ~(0x2);
> +
> + /* enable CTS/RTS auto-flow control */
> + serial_out(up, UART_REG_IER,
> + (serial_in(up, UART_REG_IER) | (0x3000)));
Once you have named the bits probably with defines, most of the comments
such as the one above are rendered redundant and should be dropped.
> +
> + /* Set hardware flow control */
> + up->port.flags |= UPF_HARD_FLOW;
> + } else {
> + /* disable CTS/RTS auto-flow control */
> + ier = serial_in(up, UART_REG_IER);
> + ier &= ~(0x3000);
> + serial_out(up, UART_REG_IER, ier);
> +
> + /* un-set hardware flow control */
> + up->port.flags &= ~UPF_HARD_FLOW;
> + }
> +
> + /* set CTS high level trigger */
> + serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) | (0x100)));
> + serial_out(up, UART_REG_MCR, mcr);
> +}
> +
> +static void ma35d1serial_break_ctl(struct uart_port *port, int break_state)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + unsigned long flags;
> + u32 lcr;
> +
> + spin_lock_irqsave(&up->port.lock, flags);
> + lcr = serial_in(up, UART_REG_LCR);
> + if (break_state != 0)
> + lcr |= BCB; /* set break */
> + else
> + lcr &= ~BCB; /* clr break */
While BCB might come from HW naming, a better name for would make these
very obvious (and the comments unnecessary).
> + serial_out(up, UART_REG_LCR, lcr);
> + spin_unlock_irqrestore(&up->port.lock, flags);
> +}
> +
> +static int ma35d1serial_startup(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + struct tty_struct *tty = port->state->port.tty;
> + int retval;
> +
> + /* Reset FIFO */
> + serial_out(up, UART_REG_FCR, TFR | RFR /* | RX_DIS */);
> +
> + /* Clear pending interrupts */
> + serial_out(up, UART_REG_ISR, 0xFFFFFFFF);
~0 is another option.
> +
> + retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
> + tty ? tty->name : "ma35d1_serial", port);
Why such exceptional name trickery?
> + if (retval) {
> + dev_err(up->port.dev, "request irq failed.\n");
> + return retval;
> + }
> +
> + /* Now, initialize the UART */
> + /* FIFO trigger level 4 byte */
> + /* RTS trigger level 8 bytes */
> + serial_out(up, UART_REG_FCR,
> + serial_in(up, UART_REG_FCR) | 0x10 | 0x20000);
> + serial_out(up, UART_REG_LCR, 0x7); /* 8 bit */
> + serial_out(up, UART_REG_TOR, 0x40);
> + serial_out(up, UART_REG_IER,
> + RTO_IEN | RDA_IEN | TIME_OUT_EN | BUFERR_IEN);
> + return 0;
> +}
> +
> +static void ma35d1serial_shutdown(struct uart_port *port)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + free_irq(port->irq, port);
> +
> + /* Disable interrupts from this port */
> + serial_out(up, UART_REG_IER, 0);
> +}
> +
> +static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
> +{
> + u32 quot;
> +
> + quot = (port->uartclk / baud) - 2;
Unnecessary parenthesis. (+Somebody already commented about the
unnecessary variable.)
> + return quot;
> +}
> +
> +static void ma35d1serial_set_termios(struct uart_port *port,
> + struct ktermios *termios,
> + const struct ktermios *old)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> + u32 lcr = 0;
> + unsigned long flags;
> + u32 baud, quot;
> +
> + switch (termios->c_cflag & CSIZE) {
> + case CS5:
> + lcr = 0;
> + break;
> + case CS6:
> + lcr |= 1;
> + break;
> + case CS7:
> + lcr |= 2;
> + break;
> + case CS8:
> + default:
> + lcr |= 3;
> + break;
> + }
> +
> + if (termios->c_cflag & CSTOPB)
> + lcr |= NSB;
> + if (termios->c_cflag & PARENB)
> + lcr |= PBE;
> + if (!(termios->c_cflag & PARODD))
> + lcr |= EPE;
> + if (termios->c_cflag & CMSPAR)
> + lcr |= SPE;
> +
> + baud = uart_get_baud_rate(port, termios, old, port->uartclk / 0xffff,
> + port->uartclk / 11);
> +
> + quot = ma35d1serial_get_divisor(port, baud);
> +
> + /*
> + * Ok, we're now changing the port state. Do it with
> + * interrupts disabled.
> + */
> + spin_lock_irqsave(&up->port.lock, flags);
> +
> + up->port.read_status_mask = RX_OVER_IF;
> + if (termios->c_iflag & INPCK)
> + up->port.read_status_mask |= FEF | PEF;
> + if (termios->c_iflag & (BRKINT | PARMRK))
> + up->port.read_status_mask |= BIF;
> +
> + /*
> + * Characteres to ignore
> + */
> + up->port.ignore_status_mask = 0;
> + if (termios->c_iflag & IGNPAR)
> + up->port.ignore_status_mask |= FEF | PEF;
> + if (termios->c_iflag & IGNBRK) {
> + up->port.ignore_status_mask |= BIF;
> + /*
> + * If we're ignoring parity and break indicators,
> + * ignore overruns too (for real raw support).
> + */
> + if (termios->c_iflag & IGNPAR)
> + up->port.ignore_status_mask |= RX_OVER_IF;
> + }
> + if (termios->c_cflag & CRTSCTS)
> + up->mcr |= UART_MCR_AFE;
> + else
> + up->mcr &= ~UART_MCR_AFE;
> +
> + ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
> + serial_out(up, UART_REG_BAUD, quot | 0x30000000);
> + serial_out(up, UART_REG_LCR, lcr);
You need to do uart_update_timeout() in the set_termios function.
> + spin_unlock_irqrestore(&up->port.lock, flags);
> +}
> +
> +static void ma35d1serial_release_port(struct uart_port *port)
> +{
> + iounmap(port->membase);
> + port->membase = NULL;
> +}
> +
> +static int ma35d1serial_request_port(struct uart_port *port)
> +{
> + return 0;
> +}
> +
> +static void ma35d1serial_config_port(struct uart_port *port, int flags)
> +{
> + int ret;
> +
> + /*
> + * Find the region that we can probe for. This in turn
> + * tells us whether we can probe for the type of port.
> + */
> + ret = ma35d1serial_request_port(port);
> + if (ret < 0)
> + return;
> + port->type = PORT_MA35D1;
> +}
> +
> +static int ma35d1serial_verify_port(struct uart_port *port,
> + struct serial_struct *ser)
> +{
> + if (ser->type != PORT_UNKNOWN && ser->type != PORT_MA35D1)
> + return -EINVAL;
> + return 0;
> +}
> +
> +static const char *ma35d1serial_type(struct uart_port *port)
> +{
> + return (port->type == PORT_MA35D1) ? "MA35D1" : NULL;
> +}
> +
> +/* Enable or disable the rs485 support */
> +static int ma35d1serial_config_rs485(struct uart_port *port,
> + struct ktermios *termios,
> + struct serial_rs485 *rs485conf)
> +{
> + struct uart_ma35d1_port *p = to_ma35d1_uart_port(port);
> +
> + p->rs485 = *rs485conf;
> +
> + if (p->rs485.delay_rts_before_send >= 1000)
> + p->rs485.delay_rts_before_send = 1000;
Don't do this in driver, the core handles the delay limits. You don't seem
to be using the value anyway for anything???
Please separate the RS485 support into its own patch.
> + serial_out(p, UART_FUN_SEL,
> + (serial_in(p, UART_FUN_SEL) & ~FUN_SEL_MASK));
> +
> + if (rs485conf->flags & SER_RS485_ENABLED) {
> + serial_out(p, UART_FUN_SEL,
> + (serial_in(p, UART_FUN_SEL) | FUN_SEL_RS485));
Does this pair of serial_out()s glitch the RS485 line if ->rs485_config()
is called while RS485 mode is already set?
Why you need to do serial_in() from the UART_FUN_SEL twice?
> +
> + if (rs485conf->flags & SER_RS485_RTS_ON_SEND)
> + serial_out(p, UART_REG_MCR,
> + (serial_in(p, UART_REG_MCR) & ~0x200));
> + else
> + serial_out(p, UART_REG_MCR,
> + (serial_in(p, UART_REG_MCR) | 0x200));
> +
> + /* set auto direction mode */
> + serial_out(p, UART_REG_ALT_CSR,
> + (serial_in(p, UART_REG_ALT_CSR) | (1 << 10)));
> + }
> + return 0;
> +}
> +
> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
> +{
> + switch (cmd) {
> + default:
> + return -ENOIOCTLCMD;
> + }
> + return 0;
> +}
> +
> +static const struct uart_ops ma35d1serial_ops = {
> + .tx_empty = ma35d1serial_tx_empty,
> + .set_mctrl = ma35d1serial_set_mctrl,
> + .get_mctrl = ma35d1serial_get_mctrl,
> + .stop_tx = ma35d1serial_stop_tx,
> + .start_tx = ma35d1serial_start_tx,
> + .stop_rx = ma35d1serial_stop_rx,
> + .break_ctl = ma35d1serial_break_ctl,
> + .startup = ma35d1serial_startup,
> + .shutdown = ma35d1serial_shutdown,
> + .set_termios = ma35d1serial_set_termios,
> + .type = ma35d1serial_type,
> + .release_port = ma35d1serial_release_port,
> + .request_port = ma35d1serial_request_port,
> + .config_port = ma35d1serial_config_port,
> + .verify_port = ma35d1serial_verify_port,
> + .ioctl = ma35d1serial_ioctl,
> +};
> +
> +static const struct of_device_id ma35d1_serial_of_match[] = {
> + { .compatible = "nuvoton,ma35d1-uart" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, ma35d1_serial_of_match);
> +
> +#ifdef CONFIG_SERIAL_NUVOTON_MA35D1_CONSOLE
> +
> +static void ma35d1serial_console_putchar(struct uart_port *port,
> + unsigned char ch)
> +{
> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
> +
> + do {
> + } while ((serial_in(up, UART_REG_FSR) & TX_FULL));
> + serial_out(up, UART_REG_THR, ch);
> +}
> +
> +/*
> + * Print a string to the serial port trying not to disturb
> + * any possible real use of the port...
> + *
> + * The console_lock must be held when we get here.
> + */
> +static void ma35d1serial_console_write(struct console *co,
> + const char *s, u32 count)
> +{
> + struct uart_ma35d1_port *up = &ma35d1serial_ports[co->index];
> + unsigned long flags;
> + u32 ier;
> +
> + local_irq_save(flags);
> +
> + /*
> + * First save the IER then disable the interrupts
> + */
> + ier = serial_in(up, UART_REG_IER);
> + serial_out(up, UART_REG_IER, 0);
> +
> + uart_console_write(&up->port, s, count, ma35d1serial_console_putchar);
> +
> + /*
> + * Finally, wait for transmitter to become empty
> + * and restore the IER
> + */
> + do {
> + } while (!(serial_in(up, UART_REG_FSR) & TX_EMPTY));
> + serial_out(up, UART_REG_IER, ier);
> + local_irq_restore(flags);
> +}
> +
> +static int __init ma35d1serial_console_setup(struct console *co,
> + char *options)
> +{
> + struct device_node *np = ma35d1serial_uart_nodes[co->index];
> + struct uart_ma35d1_port *p = &ma35d1serial_ports[co->index];
> + u32 val32[4];
> + struct uart_port *port;
> + int baud = 115200;
> + int bits = 8;
> + int parity = 'n';
> + int flow = 'n';
> +
> + /*
> + * Check whether an invalid uart number has been specified, and
> + * if so, search for the first available port that does have
> + * console support.
> + */
> + if ((co->index < 0) || (co->index >= UART_NR)) {
> + pr_debug("Console Port%x out of range\n", co->index);
> + return -EINVAL;
> + }
> +
> + if (of_property_read_u32_array(np, "reg", val32, 4) != 0)
> + return -EINVAL;
> + p->port.iobase = val32[1];
> + p->port.membase = ioremap(p->port.iobase, 0x10000);
> + p->port.ops = &ma35d1serial_ops;
> + p->port.line = 0;
> + p->port.uartclk = 24000000;
> +
> + port = &ma35d1serial_ports[co->index].port;
> + return uart_set_options(port, co, baud, parity, bits, flow);
> +}
> +
> +static struct console ma35d1serial_console = {
> + .name = "ttyS",
> + .write = ma35d1serial_console_write,
> + .device = uart_console_device,
> + .setup = ma35d1serial_console_setup,
> + .flags = CON_PRINTBUFFER | CON_ENABLED,
> + .index = -1,
> + .data = &ma35d1serial_reg,
> +};
> +
> +static void
> +ma35d1serial_console_init_port(void)
> +{
> + int i = 0;
> + struct device_node *np;
> +
> + for_each_matching_node(np, ma35d1_serial_of_match) {
> + if (ma35d1serial_uart_nodes[i] == NULL) {
> + ma35d1serial_uart_nodes[i] = np;
> + i++;
> + }
> + }
> +}
> +
> +static int __init ma35d1serial_console_init(void)
> +{
> + ma35d1serial_console_init_port();
> + register_console(&ma35d1serial_console);
> + return 0;
> +}
> +console_initcall(ma35d1serial_console_init);
> +
> +#define MA35D1SERIAL_CONSOLE (&ma35d1serial_console)
> +#else
> +#define MA35D1SERIAL_CONSOLE NULL
> +#endif
> +
> +static struct uart_driver ma35d1serial_reg = {
> + .owner = THIS_MODULE,
> + .driver_name = "serial",
> + .dev_name = "ttyS",
> + .major = TTY_MAJOR,
> + .minor = 64,
> + .cons = MA35D1SERIAL_CONSOLE,
> + .nr = UART_NR,
> +};
> +
> +/**
> + * Suspend one serial port.
> + */
> +void ma35d1serial_suspend_port(int line)
> +{
> + uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
> +}
> +EXPORT_SYMBOL(ma35d1serial_suspend_port);
> +
> +/**
> + * Resume one serial port.
> + */
> +void ma35d1serial_resume_port(int line)
> +{
> + struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
> +
> + uart_resume_port(&ma35d1serial_reg, &up->port);
> +}
> +EXPORT_SYMBOL(ma35d1serial_resume_port);
> +
> +/*
> + * Register a set of serial devices attached to a platform device.
> + * The list is terminated with a zero flags entry, which means we expect
> + * all entries to have at least UPF_BOOT_AUTOCONF set.
> + */
> +static int ma35d1serial_probe(struct platform_device *pdev)
> +{
> + struct resource *res_mem;
> + struct uart_ma35d1_port *up;
> + int ret;
> + struct clk *clk;
> + int err;
> +
> + if (pdev->dev.of_node) {
> + ret = of_alias_get_id(pdev->dev.of_node, "serial");
> + if (ret < 0) {
> + dev_err(&pdev->dev,
> + "failed to get alias/pdev id, errno %d\n",
> + ret);
Just put error prints to one line if you don't break 100 chars limit.
> + return ret;
Misaligned line.
> + }
> + }
> + up = &ma35d1serial_ports[ret];
> + up->port.line = ret;
> + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res_mem)
> + return -ENODEV;
> +
> + up->port.iobase = res_mem->start;
> + up->port.membase = ioremap(up->port.iobase, 0x10000);
Define for the literal.
> + up->port.ops = &ma35d1serial_ops;
> +
> + spin_lock_init(&up->port.lock);
> +
> + clk = of_clk_get(pdev->dev.of_node, 0);
> + if (IS_ERR(clk)) {
> + err = PTR_ERR(clk);
> + dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
> + return -ENOENT;
> + }
> + err = clk_prepare_enable(clk);
> + if (err)
> + return -ENOENT;
> +
> + if (up->port.line != 0)
> + up->port.uartclk = clk_get_rate(clk);
> + up->port.irq = platform_get_irq(pdev, 0);
> + up->port.dev = &pdev->dev;
> + up->port.flags = UPF_BOOT_AUTOCONF;
> + up->port.rs485_config = ma35d1serial_config_rs485;
Please provide also .rs485_supported for the serial core to handle the
supported features for you.
> + ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
> + platform_set_drvdata(pdev, up);
> + return 0;
> +}
> +
> +/*
> + * Remove serial ports registered against a platform device.
> + */
> +static int ma35d1serial_remove(struct platform_device *dev)
> +{
> + int i;
> + struct uart_port *port = platform_get_drvdata(dev);
> +
> + free_irq(port->irq, port);
> + for (i = 0; i < UART_NR; i++) {
> + struct uart_ma35d1_port *up = &ma35d1serial_ports[i];
> +
> + if (up->port.dev == &dev->dev)
> + uart_remove_one_port(&ma35d1serial_reg, &up->port);
> + }
> + return 0;
> +}
> +
> +static int ma35d1serial_suspend(struct platform_device *dev,
> + pm_message_t state)
> +{
> + int i;
> + struct uart_ma35d1_port *up;
> +
> + if (dev->dev.of_node)
> + i = of_alias_get_id(dev->dev.of_node, "serial");
> + if (i < 0) {
> + dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
> + i);
Just put this to the same line with the rest.
> + return i;
> + }
> + up = &ma35d1serial_ports[i];
> + if (i == 0) {
> + up->console_baud_rate = serial_in(up, UART_REG_BAUD);
> + up->console_line = serial_in(up, UART_REG_LCR);
> + up->console_int = serial_in(up, UART_REG_IER);
> + }
> + return 0;
> +}
> +
> +static int ma35d1serial_resume(struct platform_device *dev)
> +{
> + int i;
> + struct uart_ma35d1_port *up;
> +
> + if (dev->dev.of_node)
> + i = of_alias_get_id(dev->dev.of_node, "serial");
> + if (i < 0) {
> + dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
> + i);
Same line.
> + return i;
> + }
> + up = &ma35d1serial_ports[i];
> + if (i == 0) {
> + serial_out(up, UART_REG_BAUD, up->console_baud_rate);
> + serial_out(up, UART_REG_LCR, up->console_line);
> + serial_out(up, UART_REG_IER, up->console_int);
> + }
> + return 0;
> +}
> +
> +static struct platform_driver ma35d1serial_driver = {
> + .probe = ma35d1serial_probe,
> + .remove = ma35d1serial_remove,
> + .suspend = ma35d1serial_suspend,
> + .resume = ma35d1serial_resume,
> + .driver = {
> + .name = "ma35d1-uart",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(ma35d1_serial_of_match),
> + },
> +};
> +
> +static int __init ma35d1serial_init(void)
> +{
> + int ret;
> +
> + ret = uart_register_driver(&ma35d1serial_reg);
> + if (ret)
> + return ret;
> + ret = platform_driver_register(&ma35d1serial_driver);
> + if (ret)
> + uart_unregister_driver(&ma35d1serial_reg);
> + return ret;
> +}
> +
> +static void __exit ma35d1serial_exit(void)
> +{
> + platform_driver_unregister(&ma35d1serial_driver);
> + uart_unregister_driver(&ma35d1serial_reg);
> +}
> +
> +module_init(ma35d1serial_init);
> +module_exit(ma35d1serial_exit);
> +
> +MODULE_LICENSE("GPL v2");
"GPL" is enough for MODULE_LICENSE, the SPDX at the start of file covers
more specific license variations.
> +MODULE_DESCRIPTION("MA35D1 serial driver");
> +MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR);
> +
> diff --git a/drivers/tty/serial/ma35d1_serial.h b/drivers/tty/serial/ma35d1_serial.h
> new file mode 100644
> index 000000000000..5fd845c31b29
> --- /dev/null
> +++ b/drivers/tty/serial/ma35d1_serial.h
> @@ -0,0 +1,93 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * MA35D1 serial driver header file
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + */
> +#ifndef __MA35D1_SERIAL_H__
> +#define __MA35D1_SERIAL_H__
> +
> +/* UART Receive/Transmit Buffer Register */
> +#define UART_REG_RBR 0x00
> +#define UART_REG_THR 0x00
> +
> +/* UART Interrupt Enable Register */
> +#define UART_REG_IER 0x04
> +#define RDA_IEN 0x00000001 /* RBR Available Interrupt Enable */
> +#define THRE_IEN 0x00000002 /* THR Empty Interrupt Enable */
> +#define RLS_IEN 0x00000004 /* RX Line Status Interrupt Enable */
> +#define RTO_IEN 0x00000010 /* RX Time-out Interrupt Enable */
> +#define BUFERR_IEN 0x00000020 /* Buffer Error Interrupt Enable */
> +#define TIME_OUT_EN 0x00000800 /* RX Buffer Time-out Counter Enable */
> +
> +/* UART FIFO Control Register */
> +#define UART_REG_FCR 0x08
> +#define RFR 0x00000002 /* RX Field Software Reset */
> +#define TFR 0x00000004 /* TX Field Software Reset */
> +
> +/* UART Line Control Register */
> +#define UART_REG_LCR 0x0C
> +#define NSB 0x00000004 /* Number of “STOP Bit” */
> +#define PBE 0x00000008 /* Parity Bit Enable */
> +#define EPE 0x00000010 /* Even Parity Enable */
> +#define SPE 0x00000020 /* Stick Parity Enable */
> +#define BCB 0x00000040 /* Break Control */
> +
> +/* UART Modem Control Register */
> +#define UART_REG_MCR 0x10
> +#define RTS 0x00000020 /* nRTS Signal Control */
> +#define RTSACTLV 0x00000200 /* nRTS Pin Active Level */
> +#define RTSSTS 0x00002000 /* nRTS Pin Status (Read Only) */
> +
> +/* UART Modem Status Register */
> +#define UART_REG_MSR 0x14
> +#define CTSDETF 0x00000001 /* Detect nCTS State Change Flag */
> +#define CTSSTS 0x00000010 /* nCTS Pin Status (Read Only) */
> +#define CTSACTLV 0x00000100 /* nCTS Pin Active Level */
> +
> +/* UART FIFO Status Register */
> +#define UART_REG_FSR 0x18
> +#define RX_OVER_IF 0x00000001 /* RX Overflow Error Interrupt Flag */
> +#define PEF 0x00000010 /* Parity Error Flag*/
> +#define FEF 0x00000020 /* Framing Error Flag */
> +#define BIF 0x00000040 /* Break Interrupt Flag */
> +#define RX_EMPTY 0x00004000 /* Receiver FIFO Empty (Read Only) */
> +#define RX_FULL 0x00008000 /* Receiver FIFO Full (Read Only) */
> +#define TX_EMPTY 0x00400000 /* Transmitter FIFO Empty (Read Only) */
> +#define TX_FULL 0x00800000 /* Transmitter FIFO Full (Read Only) */
> +#define TX_OVER_IF 0x01000000 /* TX Overflow Error Interrupt Flag */
> +#define TE_FLAG 0x10000000 /* Transmitter Empty Flag (Read Only) */
> +
> +/* UART Interrupt Status Register */
> +#define UART_REG_ISR 0x1C
> +#define RDA_IF 0x00000001 /* RBR Available Interrupt Flag */
> +#define THRE_IF 0x00000002 /* THR Empty Interrupt Flag */
> +#define RLSIF 0x00000004 /* Receive Line Interrupt Flag */
> +#define MODEMIF 0x00000008 /* MODEM Interrupt Flag */
> +#define RXTO_IF 0x00000010 /* RX Time-out Interrupt Flag */
> +#define BUFEIF 0x00000020 /* Buffer Error Interrupt Flag */
> +#define WK_IF 0x00000040 /* UART Wake-up Interrupt Flag */
> +#define RDAINT 0x00000100 /* RBR Available Interrupt Indicator */
> +#define THRE_INT 0x00000200 /* THR Empty Interrupt Indicator */
> +
> +/* UART Time-out Register */
> +#define UART_REG_TOR 0x20
> +
> +/* UART Baud Rate Divider Register */
> +#define UART_REG_BAUD 0x24
> +
> +/* UART Alternate Control/Status Register */
> +#define UART_REG_ALT_CSR 0x2C
> +
> +/* UART Function Select Register */
> +#define UART_FUN_SEL 0x30
> +#define FUN_SEL_UART 0x00000000
> +#define FUN_SEL_RS485 0x00000003
> +#define FUN_SEL_MASK 0x00000007
GENMASK(), then use FIELD_PREP() for the values.
> +
> +/* UART Wake-up Control Register */
> +#define UART_REG_WKCTL 0x40
> +
> +/* UART Wake-up Status Register */
> +#define UART_REG_WKSTS 0x44
> +
> +#endif /* __MA35D1_SERIAL_H__ */
> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
> index 281fa286555c..c6d53db17042 100644
> --- a/include/uapi/linux/serial_core.h
> +++ b/include/uapi/linux/serial_core.h
> @@ -279,4 +279,7 @@
> /* Sunplus UART */
> #define PORT_SUNPLUS 123
>
> +/* Nuvoton MA35D1 UART */
> +#define PORT_MA35D1 124
> +
> #endif /* _UAPILINUX_SERIAL_CORE_H */
>
--
i.
On Wed, 15 Mar 2023, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> This driver supports individual IP reset for ma35d1. The reset
> control registers is a subset of system control registers.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> drivers/reset/Kconfig | 6 ++
> drivers/reset/Makefile | 1 +
> drivers/reset/reset-ma35d1.c | 152 +++++++++++++++++++++++++++++++++++
> 3 files changed, 159 insertions(+)
> create mode 100644 drivers/reset/reset-ma35d1.c
>
> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> index 2a52c990d4fe..47671060d259 100644
> --- a/drivers/reset/Kconfig
> +++ b/drivers/reset/Kconfig
> @@ -143,6 +143,12 @@ config RESET_NPCM
> This enables the reset controller driver for Nuvoton NPCM
> BMC SoCs.
>
> +config RESET_NUVOTON_MA35D1
> + bool "Nuvton MA35D1 Reset Driver"
> + default ARCH_NUVOTON
> + help
> + This enables the reset controller driver for Nuvoton MA35D1 SoC.
> +
> config RESET_OXNAS
> bool
>
> diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
> index 3e7e5fd633a8..fd52dcf66a99 100644
> --- a/drivers/reset/Makefile
> +++ b/drivers/reset/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_RESET_MCHP_SPARX5) += reset-microchip-sparx5.o
> obj-$(CONFIG_RESET_MESON) += reset-meson.o
> obj-$(CONFIG_RESET_MESON_AUDIO_ARB) += reset-meson-audio-arb.o
> obj-$(CONFIG_RESET_NPCM) += reset-npcm.o
> +obj-$(CONFIG_RESET_NUVOTON_MA35D1) += reset-ma35d1.o
> obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
> obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
> obj-$(CONFIG_RESET_POLARFIRE_SOC) += reset-mpfs.o
> diff --git a/drivers/reset/reset-ma35d1.c b/drivers/reset/reset-ma35d1.c
> new file mode 100644
> index 000000000000..bdd39483ca4e
> --- /dev/null
> +++ b/drivers/reset/reset-ma35d1.c
> @@ -0,0 +1,152 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset-controller.h>
> +#include <linux/mfd/ma35d1-sys.h>
> +#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
> +#include <linux/regmap.h>
> +#include <linux/reboot.h>
> +
> +#define RST_PRE_REG 32
> +
> +struct ma35d1_reset_data {
> + struct reset_controller_dev rcdev;
> + struct regmap *regmap;
> +};
> +
> +struct ma35d1_reboot_data {
> + struct notifier_block restart_handler;
> + struct regmap *regmap;
> +};
> +
> +static int ma35d1_restart_handler(struct notifier_block *this,
> + unsigned long mode, void *cmd)
> +{
> + struct ma35d1_reboot_data *data =
> + container_of(this, struct ma35d1_reboot_data,
> + restart_handler);
> + regmap_write(data->regmap, REG_SYS_IPRST0, 1 << MA35D1_RESET_CHIP);
> + return -EAGAIN;
This results -EAGAIN always???
> +}
> +
> +static int ma35d1_reset_update(struct reset_controller_dev *rcdev,
> + unsigned long id, bool assert)
> +{
> + int reg;
> + int offset = (id / RST_PRE_REG) * 4;
> + struct ma35d1_reset_data *data =
> + container_of(rcdev, struct ma35d1_reset_data, rcdev);
> +
> + regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
> + if (assert)
> + reg |= 1 << (id % RST_PRE_REG);
> + else
> + reg &= ~(1 << (id % RST_PRE_REG));
> +
> + regmap_write(data->regmap, REG_SYS_IPRST0 + offset, reg);
> + return 0;
This returns always 0. What about regmap_read/write() errors, should the
be returned?
> +}
> +
> +static int ma35d1_reset_assert(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + return ma35d1_reset_update(rcdev, id, true);
> +}
> +
> +static int ma35d1_reset_deassert(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + return ma35d1_reset_update(rcdev, id, false);
> +}
> +
> +static int ma35d1_reset_status(struct reset_controller_dev *rcdev,
> + unsigned long id)
> +{
> + int reg;
> + int offset = id / RST_PRE_REG;
> + struct ma35d1_reset_data *data =
> + container_of(rcdev, struct ma35d1_reset_data, rcdev);
> +
> + regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
Error handling?
> + return !!(reg & BIT(id % RST_PRE_REG));
> +}
> +
> +static const struct reset_control_ops ma35d1_reset_ops = {
> + .assert = ma35d1_reset_assert,
> + .deassert = ma35d1_reset_deassert,
> + .status = ma35d1_reset_status,
> +};
> +
> +static const struct of_device_id ma35d1_reset_dt_ids[] = {
> + { .compatible = "nuvoton,ma35d1-reset" },
> + { },
> +};
> +
> +static int ma35d1_reset_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct ma35d1_reset_data *reset_data;
> + struct ma35d1_reboot_data *reboot_data;
> + int err;
> +
> + if (!pdev->dev.of_node) {
> + dev_err(&pdev->dev, "Device tree node not found\n");
> + return -EINVAL;
> + }
> +
> + reset_data = devm_kzalloc(dev, sizeof(*reset_data), GFP_KERNEL);
> + if (!reset_data)
> + return -ENOMEM;
> +
> + reboot_data = devm_kzalloc(dev, sizeof(*reboot_data), GFP_KERNEL);
> + if (!reboot_data) {
> + devm_kfree(dev, reset_data);
Unnecessary.
> + return -ENOMEM;
> + }
> +
> + reset_data->regmap = syscon_regmap_lookup_by_phandle(
> + pdev->dev.of_node, "regmap");
> + if (IS_ERR(reset_data->regmap)) {
> + dev_err(&pdev->dev, "Failed to get SYS register base\n");
> + err = PTR_ERR(reset_data->regmap);
> + goto err_out;
> + }
> + reset_data->rcdev.owner = THIS_MODULE;
> + reset_data->rcdev.nr_resets = MA35D1_RESET_COUNT;
> + reset_data->rcdev.ops = &ma35d1_reset_ops;
> + reset_data->rcdev.of_node = dev->of_node;
> +
> + reboot_data->regmap = reset_data->regmap;
> + reboot_data->restart_handler.notifier_call = ma35d1_restart_handler;
> + reboot_data->restart_handler.priority = 192;
> +
> + err = register_restart_handler(&reboot_data->restart_handler);
> + if (err)
> + dev_warn(&pdev->dev, "failed to register restart handler\n");
> +
> + return devm_reset_controller_register(dev, &reset_data->rcdev);
> +
> +err_out:
> + devm_kfree(dev, reset_data);
> + devm_kfree(dev, reboot_data);
These are unnecessary since the probe is failing.
> + return err;
> +}
> +
> +static struct platform_driver ma35d1_reset_driver = {
> + .probe = ma35d1_reset_probe,
> + .driver = {
> + .name = "ma35d1-reset",
> + .of_match_table = ma35d1_reset_dt_ids,
> + },
> +};
> +
> +builtin_platform_driver(ma35d1_reset_driver);
>
--
i.
On Wed, 15 Mar 2023, Jacky Huang wrote:
> From: Jacky Huang <[email protected]>
>
> The clock controller generates clocks for the whole chip, including
> system clocks and all peripheral clocks. This driver support ma35d1
> clock gating, divider, and individual PLL configuration.
>
> There are 6 PLLs in ma35d1 SoC:
> - CA-PLL for the two Cortex-A35 CPU clock
> - SYS-PLL for system bus, which comes from the companion MCU
> and cannot be programmed by clock controller.
> - DDR-PLL for DDR
> - EPLL for GMAC and GFX, Display, and VDEC IPs.
> - VPLL for video output pixel clock
> - APLL for SDHC, I2S audio, and other IPs.
> CA-PLL has only one operation mode.
> DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
> operation modes: integer mode, fraction mode, and spread specturm mode.
>
> Signed-off-by: Jacky Huang <[email protected]>
> ---
> drivers/clk/Makefile | 1 +
> drivers/clk/nuvoton/Makefile | 4 +
> drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 ++++
> drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 +++++++++++++
> drivers/clk/nuvoton/clk-ma35d1.c | 970 +++++++++++++++++++++++
> drivers/clk/nuvoton/clk-ma35d1.h | 198 +++++
> 6 files changed, 1851 insertions(+)
> create mode 100644 drivers/clk/nuvoton/Makefile
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
>
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index e3ca0d058a25..2e7916d269e1 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -103,6 +103,7 @@ endif
> obj-y += mstar/
> obj-y += mvebu/
> obj-$(CONFIG_ARCH_MXS) += mxs/
> +obj-$(CONFIG_ARCH_NUVOTON) += nuvoton/
> obj-$(CONFIG_COMMON_CLK_NXP) += nxp/
> obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pistachio/
> obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
> diff --git a/drivers/clk/nuvoton/Makefile b/drivers/clk/nuvoton/Makefile
> new file mode 100644
> index 000000000000..d2c092541b8d
> --- /dev/null
> +++ b/drivers/clk/nuvoton/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1.o
> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-divider.o
> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-pll.o
> diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c
> new file mode 100644
> index 000000000000..5f4791531e47
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c
> @@ -0,0 +1,144 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/spinlock.h>
> +
> +#include "clk-ma35d1.h"
> +
> +#define div_mask(width) ((1 << (width)) - 1)
> +
> +struct ma35d1_adc_clk_divider {
> + struct clk_hw hw;
> + void __iomem *reg;
> + u8 shift;
> + u8 width;
> + u32 mask;
> + const struct clk_div_table *table;
> + spinlock_t *lock;
Add comment to indicate what it protects.
> +};
> +
> +#define to_ma35d1_adc_clk_divider(_hw) \
> + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
static inline
> +static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + unsigned int val;
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + val = readl_relaxed(dclk->reg) >> dclk->shift;
> + val &= div_mask(dclk->width);
> + val += 1;
> + return divider_recalc_rate(hw, parent_rate, val, dclk->table,
> + CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
> +}
> +
> +static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + return divider_round_rate(hw, rate, prate, dclk->table,
> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
> +}
> +
> +static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + int value;
> + unsigned long flags = 0;
> + u32 data;
> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
> +
> + value = divider_get_val(rate, parent_rate, dclk->table,
> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
> +
> + if (dclk->lock)
> + spin_lock_irqsave(dclk->lock, flags);
> +
> + data = readl_relaxed(dclk->reg);
> + data &= ~(div_mask(dclk->width) << dclk->shift);
> + data |= (value - 1) << dclk->shift;
> + data |= dclk->mask;
> +
> + writel_relaxed(data, dclk->reg);
> +
> + if (dclk->lock)
> + spin_unlock_irqrestore(dclk->lock, flags);
> +
> + return 0;
> +}
> +
> +static const struct clk_ops ma35d1_adc_clkdiv_ops = {
> + .recalc_rate = ma35d1_clkdiv_recalc_rate,
> + .round_rate = ma35d1_clkdiv_round_rate,
> + .set_rate = ma35d1_clkdiv_set_rate,
> +};
> +
> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
> + const char *parent_name,
> + unsigned long flags, void __iomem *reg,
> + u8 shift, u8 width, u32 mask_bit)
> +{
> + struct ma35d1_adc_clk_divider *div;
> + struct clk_init_data init;
> + struct clk_div_table *table;
> + u32 max_div, min_div;
> + struct clk_hw *hw;
> + int ret;
> + int i;
> +
> + /* allocate the divider */
> + div = kzalloc(sizeof(*div), GFP_KERNEL);
> + if (!div)
> + return ERR_PTR(-ENOMEM);
> +
> + /* Init the divider table */
> + max_div = div_mask(width) + 1;
> + min_div = 1;
> +
> + table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
> + if (!table) {
> + kfree(div);
> + return ERR_PTR(-ENOMEM);
Use rollback to do error handling:
ret = ERR_PTR(-ENOMEM);
goto free_div;
> + }
> +
> + for (i = 0; i < max_div; i++) {
> + table[i].val = (min_div + i);
> + table[i].div = 2 * table[i].val;
> + }
> + table[max_div].val = 0;
> + table[max_div].div = 0;
> +
> + init.name = name;
> + init.ops = &ma35d1_adc_clkdiv_ops;
> + init.flags |= flags;
> + init.parent_names = parent_name ? &parent_name : NULL;
> + init.num_parents = parent_name ? 1 : 0;
> +
> + /* struct ma35d1_adc_clk_divider assignments */
> + div->reg = reg;
> + div->shift = shift;
> + div->width = width;
> + div->mask = mask_bit ? BIT(mask_bit) : 0;
> + div->lock = &ma35d1_lock;
> + div->hw.init = &init;
> + div->table = table;
> +
> + /* Register the clock */
> + hw = &div->hw;
> + ret = clk_hw_register(NULL, hw);
> + if (ret) {
> + kfree(table);
> + kfree(div);
> + return ERR_PTR(ret);
ret = ERR_PTR(ret);
goto free_table;
> + }
> + return hw;
free_table:
kfree(table);
free_div:
kfree(div);
return ret;
> +}
> diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> new file mode 100644
> index 000000000000..79e724b148fa
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> @@ -0,0 +1,534 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/bitfield.h>
> +
> +#include "clk-ma35d1.h"
> +
> +#define to_ma35d1_clk_pll(clk) \
> + (container_of(clk, struct ma35d1_clk_pll, clk))
static inline
> +
> +#define PLL0CTL0_FBDIV_MSK GENMASK(7, 0)
> +#define PLL0CTL0_INDIV_MSK GENMASK(11, 8)
> +#define PLL0CTL0_OUTDIV_MSK GENMASK(13, 12)
> +#define PLL0CTL0_PD_MSK BIT(16)
> +#define PLL0CTL0_BP_MSK BIT(17)
> +#define PLLXCTL0_FBDIV_MSK GENMASK(10, 0)
> +#define PLLXCTL0_INDIV_MSK GENMASK(17, 12)
> +#define PLLXCTL0_MODE_MSK GENMASK(19, 18)
> +#define PLLXCTL0_SSRATE_MSK GENMASK(30, 20)
> +#define PLLXCTL1_PD_MSK BIT(0)
> +#define PLLXCTL1_BP_MSK BIT(1)
> +#define PLLXCTL1_OUTDIV_MSK GENMASK(6, 4)
> +#define PLLXCTL1_FRAC_MSK GENMASK(31, 8)
> +#define PLLXCTL2_SLOPE_MSK GENMASK(23, 0)
> +
> +struct ma35d1_clk_pll {
> + struct clk_hw hw;
> + u8 type;
> + u8 mode;
> + unsigned long rate;
> + void __iomem *ctl0_base;
> + void __iomem *ctl1_base;
> + void __iomem *ctl2_base;
> + struct regmap *regmap;
> +};
> +
> +struct vsipll_freq_conf_reg_tbl {
> + unsigned long freq;
> + u8 mode;
> + u32 ctl0_reg;
> + u32 ctl1_reg;
> + u32 ctl2_reg;
> +};
> +
> +static const struct vsipll_freq_conf_reg_tbl ma35d1pll_freq[] = {
> + { 1000000000, VSIPLL_INTEGER_MODE, 0x307d, 0x10, 0 },
> + { 884736000, VSIPLL_FRACTIONAL_MODE, 0x41024, 0xdd2f1b11, 0 },
> + { 533000000, VSIPLL_SS_MODE, 0x12b8102c, 0x6aaaab20, 0x12317 },
> + { }
> +};
> +
> +static void CLK_UnLockReg(struct ma35d1_clk_pll *pll)
Use lowercase only.
> +{
> + int ret;
> +
> + /* Unlock PLL registers */
> + do {
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x59);
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x16);
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x88);
> + regmap_read(pll->regmap, REG_SYS_RLKTZNS, &ret);
> + } while (ret == 0);
> +}
> +
> +static void CLK_LockReg(struct ma35d1_clk_pll *pll)
Ditto.
> +{
> + /* Lock PLL registers */
> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x0);
> +}
> +
> +/* SMIC PLL for CAPLL */
> +unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
> + unsigned long PllSrcClk)
Lowercase only for function name and variable names. Please do the rest,
I won't mention more of them.
> +{
> + u32 u32M, u32N, u32P, u32OutDiv;
> + u32 val;
> + unsigned long u64PllClk;
> + u32 clk_div_table[] = { 1, 2, 4, 8};
Inconsistent whitespaces.
> +
> + val = __raw_readl(pll->ctl0_base);
> +
> + u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
> + u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
> + u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
> + u32OutDiv = clk_div_table[u32P];
> +
> + if (val & PLL0CTL0_BP_MSK) {
> + u64PllClk = PllSrcClk;
> + } else {
> + u64PllClk = PllSrcClk * u32N;
> + do_div(u64PllClk, u32M * u32OutDiv);
Does this block depend on unsigned long being 64-bit? Or should you
enforce it by using u64 that is always same sized unlike unsigned long?
> + }
> + return u64PllClk;
> +}
> +
> +/* VSI-PLL: INTEGER_MODE */
> +unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
> + unsigned long u64PllFreq, u32 *u32Reg)
> +{
> + u32 u32TmpM, u32TmpN, u32TmpP;
> + u32 u32RngMinN, u32RngMinM, u32RngMinP;
> + u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
> + u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
Remove types from names.
> + unsigned long u64PllClk;
> + unsigned long u64Con1, u64Con2, u64Con3;
Okay as unsigned long or do you want always 64-bit which is u64 ?
Define these inside the loops below and remove the types from the name.
> +
> + u64PllClk = 0;
> + u32Min = (u32) -1;
> +
> + if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
> + (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
> + u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
> + u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
> + u64PllClk = ma35d1pll_freq[0].freq;
> + return u64PllClk;
> + }
> +
> + u32RngMinM = 1UL;
> + u32RngMaxM = 63UL;
> + u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
> + (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
max(PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ, 1UL);
Remember to add include for it.
> + u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
> + (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
min();
> +
> + for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
<= and remove + 1
Why can't you call this loop cariable just m ?
> + u64Con1 = PllSrcClk / u32TmpM;
> + u32RngMinN = 16UL;
> + u32RngMaxN = 2047UL;
Why aren't these two values in defines?
> + u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
> + (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
max();
> + u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
> + (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
min();
> +
> + for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
<= and remove + 1
Name variable as n ?
> + u32TmpN++) {
One line.
> + u64Con2 = u64Con1 * u32TmpN;
> + u32RngMinP = 1UL;
> + u32RngMaxP = 7UL;
Limits to defines?
> + u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
> + (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
> + u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
> + u32RngMaxP) ?
> + (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
> + u32RngMaxP;
min & max.
> + for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
<= and remove +1?
Name variable as p?
> + u32TmpP++) {
One line.
> + u64Con3 = u64Con2 / u32TmpP;
> + if (u64Con3 > u64PllFreq)
> + u32Tmp = u64Con3 - u64PllFreq;
> + else
> + u32Tmp = u64PllFreq - u64Con3;
abs()?
> +
> + if (u32Tmp < u32Min) {
> + u32Min = u32Tmp;
> + u32MinM = u32TmpM;
> + u32MinN = u32TmpN;
> + u32MinP = u32TmpP;
> +
> + if (u32Min == 0UL) {
goto out?
> + u32Reg[0] = (u32MinM << 12) |
> + (u32MinN);
> + u32Reg[1] = (u32MinP << 4);
> + return ((PllSrcClk * u32MinN) /
> + (u32MinP * u32MinM));
> + }
> + }
> + }
> + }
> + }
> +
out: ?
> + u32Reg[0] = (u32MinM << 12) | (u32MinN);
FIELD_PREP() | FIELD_PREP() ?
> + u32Reg[1] = (u32MinP << 4);
FIELD_PREP() ?
> + u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
Use the 64-bit divide from math64.h rather than leave it up to compiler.
> + return u64PllClk;
> +}
> +
> +/* VSI-PLL: FRACTIONAL_MODE */
> +unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
> + unsigned long u64PllFreq, u32 *u32Reg)
> +{
> + unsigned long u64X, u64N, u64M, u64P, u64tmp;
> + unsigned long u64PllClk, u64FCLKO;
> + u32 u32FRAC;
> +
> + if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
> + u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
> + u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
> + u64PllClk = ma35d1pll_freq[1].freq;
> + return u64PllClk;
> + }
> +
> + if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
> + u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
> + ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
You need to rework this to do 64-bit divide and remainder with
something that comes from math64.h.
> + } else {
> + pr_err("Failed to set rate %ld\n", u64PllFreq);
> + return 0;
> + }
> +
> + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
> + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
> + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
Ditto.
Is here some ...ROUND_UP() trick hidden too?
> +
> + if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
> + (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
> + return 0;
> +
> + u64M = (PllSrcClk <= VSIPLL_FREFDIVM_MAX_FREQ) ? 1 :
> + ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) +
> + ((PllSrcClk % VSIPLL_FREFDIVM_MAX_FREQ) ? 1 : 0));
Ditto.
> +
> + u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
> + u64N = u64tmp / 1000;
> + u64X = u64tmp % 1000;
math64.h x 3 (or x2 since you can get remainder for free I think).
> + u32FRAC = ((u64X << 24) + 500) / 1000;
> + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
> +
> + u32Reg[0] = (u64M << 12) | (u64N);
FIELD_PREP() ?
> + u32Reg[1] = (u64P << 4) | (u32FRAC << 8);
FIELD_PREP() ?
> + return u64PllClk;
> +}
> +
> +/* VSI-PLL: SS_MODE */
> +unsigned long CLK_CalPLLFreq_Mode2(unsigned long PllSrcClk,
> + unsigned long u64PllFreq,
> + u32 u32SR, u32 u32Fmod, u32 *u32Reg)
> +{
> + unsigned long u64X, u64N, u64M, u64P, u64tmp, u64tmpP, u64tmpM;
> + unsigned long u64SSRATE, u64SLOPE, u64PllClk, u64FCLKO;
> + u32 u32FRAC, i;
> +
> + if (u64PllFreq >= VSIPLL_FCLKO_MAX_FREQ) {
> + u32Reg[0] = ma35d1pll_freq[2].ctl0_reg;
> + u32Reg[1] = ma35d1pll_freq[2].ctl1_reg;
> + u32Reg[2] = ma35d1pll_freq[2].ctl2_reg;
> + u64PllClk = ma35d1pll_freq[2].freq;
> + return u64PllClk;
> + }
> +
> + if (u64PllFreq < VSIPLL_FCLKO_MIN_FREQ) {
> + u64FCLKO = 0;
> + for (i = 2; i < 8; i++) {
> + u64tmp = (i * u64PllFreq);
> + if (u64tmp > VSIPLL_FCLKO_MIN_FREQ)
VSIPLL_FCLKO_MAX_FREQ check is not required ?
> + u64FCLKO = u64tmp;
> + }
> + if (u64FCLKO == 0) {
> + pr_err("Failed to set rate %ld\n", u64PllFreq);
> + return 0;
> + }
> +
> + } else
> + u64FCLKO = u64PllFreq;
> +
> + u64P = 0;
> + for (i = 1; i < 8; i++) {
> + u64tmpP = i * u64FCLKO;
> + if ((u64tmpP <= VSIPLL_FCLK_MAX_FREQ) &&
> + (u64tmpP >= VSIPLL_FCLK_MIN_FREQ)) {
> + u64P = i;
> + break;
> + }
> + }
> +
> + if (u64P == 0)
> + return 0;
> +
> + u64M = 0;
> + for (i = 1; i < 64; i++) {
> + u64tmpM = PllSrcClk / i;
> + if ((u64tmpM <= VSIPLL_FREFDIVM_MAX_FREQ) &&
> + (u64tmpM >= VSIPLL_FREFDIVM_MIN_FREQ1)) {
> + u64M = i;
> + break;
> + }
> + }
> +
> + if (u64M == 0)
> + return 0;
> +
> + u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
> + u64N = u64tmp / 1000;
> + u64X = u64tmp % 1000;
> + u32FRAC = ((u64X << 24) + 500) / 1000;
> +
> + u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
> + u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
> +
> + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
Is some *SEC_PER_*SEC define relevant for 1000 ?
Or some other units, e.g., HZ related?
> +
> + u32Reg[0] = (u64SSRATE << VSIPLLCTL0_SSRATE_POS) | (u64M <<
> + VSIPLLCTL0_INDIV_POS) | (u64N);
FIELD_PREP()
> + u32Reg[1] = (u64P << VSIPLLCTL1_OUTDIV_POS) | (u32FRAC << VSIPLLCTL1_FRAC_POS);
Instead of _POS named variables, add GENMASK one instead and use
FIELD_PREP. You might need to use GENMASK_ULL() for the masks if you are
dealing with true 64-bitness here instead of the quasi unsigned longs.
> + u32Reg[2] = u64SLOPE;
> + return u64PllClk;
> +}
> +
> +unsigned long CLK_SetPLLFreq(struct ma35d1_clk_pll *pll,
> + unsigned long PllSrcClk,
> + unsigned long u64PllFreq)
> +{
> + u32 u32Reg[3] = { 0 }, val_ctl0, val_ctl1, val_ctl2;
> + unsigned long u64PllClk;
> +
> + val_ctl0 = __raw_readl(pll->ctl0_base);
> + val_ctl1 = __raw_readl(pll->ctl1_base);
> + val_ctl2 = __raw_readl(pll->ctl2_base);
> +
> + switch (pll->mode) {
> + case VSIPLL_INTEGER_MODE:
> + u64PllClk = CLK_CalPLLFreq_Mode0(PllSrcClk, u64PllFreq,
> + u32Reg);
One line.
> + val_ctl0 = u32Reg[0] |
> + (VSIPLL_INTEGER_MODE << VSIPLLCTL0_MODE_POS);
GENMASK() + FIELD_PREP()
> + break;
> + case VSIPLL_FRACTIONAL_MODE:
> + u64PllClk = CLK_CalPLLFreq_Mode1(PllSrcClk, u64PllFreq,
> + u32Reg);
> + val_ctl0 = u32Reg[0] |
> + (VSIPLL_FRACTIONAL_MODE << VSIPLLCTL0_MODE_POS);
Ditto.
> + break;
> + case VSIPLL_SS_MODE:
> + u64PllClk = CLK_CalPLLFreq_Mode2(PllSrcClk, u64PllFreq,
> + VSIPLL_MODULATION_FREQ,
> + VSIPLL_SPREAD_RANGE, u32Reg);
> + val_ctl0 = u32Reg[0] |
> + (VSIPLL_SS_MODE << VSIPLLCTL0_MODE_POS);
Ditto.
> + break;
> + }
> +
> + val_ctl1 = VSIPLLCTL1_PD_MSK | u32Reg[1];
> + val_ctl2 = u32Reg[2];
> +
> + __raw_writel(val_ctl0, pll->ctl0_base);
> + __raw_writel(val_ctl1, pll->ctl1_base);
> + __raw_writel(val_ctl2, pll->ctl2_base);
> + return u64PllClk;
> +}
> +
> +unsigned long CLK_GetPLLFreq_VSIPLL(struct ma35d1_clk_pll *pll,
> + unsigned long PllSrcClk)
> +{
> + u32 u32M, u32N, u32P, u32X, u32SR, u32FMOD;
> + u32 val_ctl0, val_ctl1, val_ctl2;
> + unsigned long u64PllClk, u64X;
> +
> + val_ctl0 = __raw_readl(pll->ctl0_base);
> + val_ctl1 = __raw_readl(pll->ctl1_base);
> + val_ctl2 = __raw_readl(pll->ctl2_base);
> +
> + if (val_ctl1 & PLLXCTL1_BP_MSK) {
> + u64PllClk = PllSrcClk;
> + return u64PllClk;
> + }
> +
> + if (pll->mode == VSIPLL_INTEGER_MODE) {
> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
> +
> + u64PllClk = PllSrcClk * u32N;
> + do_div(u64PllClk, u32M * u32P);
> +
> + } else if (pll->mode == VSIPLL_FRACTIONAL_MODE) {
> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
> + u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
> + u64X = (u64) u32X;
> + u64X = (((u64X * 1000) + 500) >> 24);
> + u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
> + 1000 / u32P / u32M;
math64.h
Please fix the remaining ones w/o me noting them down.
> +
> + } else {
> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
> + u32SR = FIELD_GET(PLLXCTL0_SSRATE_MSK, val_ctl0);
> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
> + u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
> + u32FMOD = FIELD_GET(PLLXCTL2_SLOPE_MSK, val_ctl2);
> + u64X = (u64) u32X;
> + u64X = ((u64X * 1000) >> 24);
> + u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
> + 1000 / u32P / u32M;
> + }
> + return u64PllClk;
> +}
> +
> +static int ma35d1_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long parent_rate)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> +
> + if ((parent_rate < VSIPLL_FREF_MIN_FREQ) ||
> + (parent_rate > VSIPLL_FREF_MAX_FREQ))
> + return 0;
> +
> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
> + return -EACCES;
> + }
> + CLK_UnLockReg(pll);
> + pll->rate = CLK_SetPLLFreq(pll, parent_rate, rate);
> + CLK_LockReg(pll);
> + return 0;
> +}
> +
> +static unsigned long ma35d1_clk_pll_recalc_rate(struct clk_hw *hw,
> + unsigned long parent_rate)
> +{
> + unsigned long pllfreq;
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> +
> + if ((parent_rate < VSIPLL_FREF_MIN_FREQ)
> + || (parent_rate > VSIPLL_FREF_MAX_FREQ))
> + return 0;
> +
> + switch (pll->type) {
> + case MA35D1_CAPLL:
> + pllfreq = CLK_GetPLLFreq_SMICPLL(pll, parent_rate);
> + break;
> + case MA35D1_DDRPLL:
> + case MA35D1_APLL:
> + case MA35D1_EPLL:
> + case MA35D1_VPLL:
> + pllfreq = CLK_GetPLLFreq_VSIPLL(pll, parent_rate);
> + break;
> + }
> +
> + return pllfreq;
> +}
> +
> +static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> + unsigned long *prate)
> +{
> + return rate;
> +}
> +
> +static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val = __raw_readl(pll->ctl1_base);
> +
> + return (val & VSIPLLCTL1_PD_MSK) ? 0 : 1;
Unnecessary parenthesis
> +}
> +
> +static int ma35d1_clk_pll_prepare(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val;
> +
> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
> + return -EACCES;
> + }
Add helper for this, there is more than 1 copy of this.
> +
> + CLK_UnLockReg(pll);
> + val = __raw_readl(pll->ctl1_base);
> + val &= ~VSIPLLCTL1_PD_MSK;
> + __raw_writel(val, pll->ctl1_base);
> + CLK_LockReg(pll);
> + return 0;
> +}
> +
> +static void ma35d1_clk_pll_unprepare(struct clk_hw *hw)
> +{
> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
> + u32 val;
> +
> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
> + } else {
> + val = __raw_readl(pll->ctl1_base);
> + val |= VSIPLLCTL1_PD_MSK;
> + __raw_writel(val, pll->ctl1_base);
> + }
> +}
> +
> +static const struct clk_ops ma35d1_clk_pll_ops = {
> + .is_prepared = ma35d1_clk_pll_is_prepared,
> + .prepare = ma35d1_clk_pll_prepare,
> + .unprepare = ma35d1_clk_pll_unprepare,
> + .set_rate = ma35d1_clk_pll_set_rate,
> + .recalc_rate = ma35d1_clk_pll_recalc_rate,
> + .round_rate = ma35d1_clk_pll_round_rate,
> +};
> +
> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type,
> + u8 u8mode, const char *name,
> + const char *parent,
> + unsigned long targetFreq,
> + void __iomem *base,
> + struct regmap *regmap)
> +{
> + struct ma35d1_clk_pll *pll;
> + struct clk_hw *hw;
> + struct clk_init_data init;
> + int ret;
> +
> + pll = kmalloc(sizeof(*pll), GFP_KERNEL);
> + if (!pll)
> + return ERR_PTR(-ENOMEM);
> +
> + pll->type = type;
> + pll->mode = u8mode;
> + pll->rate = targetFreq;
> + pll->ctl0_base = base + VSIPLL_CTL0;
> + pll->ctl1_base = base + VSIPLL_CTL1;
> + pll->ctl2_base = base + VSIPLL_CTL2;
> + pll->regmap = regmap;
> +
> + init.name = name;
> + init.flags = 0;
> + init.parent_names = &parent;
> + init.num_parents = 1;
> + init.ops = &ma35d1_clk_pll_ops;
> + pll->hw.init = &init;
> + hw = &pll->hw;
> +
> + ret = clk_hw_register(NULL, hw);
> + if (ret) {
> + pr_err("failed to register vsi-pll clock!!!\n");
No need to use ! let alone 3 of them.
> + kfree(pll);
> + return ERR_PTR(ret);
> + }
> + return hw;
> +}
> diff --git a/drivers/clk/nuvoton/clk-ma35d1.c b/drivers/clk/nuvoton/clk-ma35d1.c
> new file mode 100644
> index 000000000000..ac8154458b81
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1.c
> @@ -0,0 +1,970 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/spinlock.h>
> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
> +
> +#include "clk-ma35d1.h"
> +
> +DEFINE_SPINLOCK(ma35d1_lock);
> +
> +static const char *const ca35clk_sel_clks[] = {
> + "hxt", "capll", "ddrpll", "dummy"
> +};
> +
> +static const char *const sysclk0_sel_clks[] = {
> + "epll_div2", "syspll"
> +};
> +
> +static const char *const sysclk1_sel_clks[] = {
> + "hxt", "syspll"
> +};
> +
> +static const char *const axiclk_sel_clks[] = {
> + "capll_div2", "capll_div4"
> +};
> +
> +static const char *const ccap_sel_clks[] = {
> + "hxt", "vpll", "apll", "syspll"
> +};
> +
> +static const char *const sdh_sel_clks[] = {
> + "syspll", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const dcu_sel_clks[] = {
> + "epll_div2", "syspll"
> +};
> +
> +static const char *const gfx_sel_clks[] = {
> + "epll", "syspll"
> +};
> +
> +static const char *const dbg_sel_clks[] = {
> + "hirc", "syspll"
> +};
> +
> +static const char *const timer0_sel_clks[] = {
> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer1_sel_clks[] = {
> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer2_sel_clks[] = {
> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer3_sel_clks[] = {
> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer4_sel_clks[] = {
> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer5_sel_clks[] = {
> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer6_sel_clks[] = {
> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer7_sel_clks[] = {
> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer8_sel_clks[] = {
> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer9_sel_clks[] = {
> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer10_sel_clks[] = {
> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const timer11_sel_clks[] = {
> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
> +};
> +
> +static const char *const uart_sel_clks[] = {
> + "hxt", "sysclk1_div2", "dummy", "dummy"
> +};
> +
> +static const char *const wdt0_sel_clks[] = {
> + "dummy", "lxt", "pclk3_div4096", "lirc"
> +};
> +
> +static const char *const wdt1_sel_clks[] = {
> + "dummy", "lxt", "pclk3_div4096", "lirc"
> +};
> +
> +static const char *const wdt2_sel_clks[] = {
> + "dummy", "lxt", "pclk4_div4096", "lirc"
> +};
> +
> +static const char *const wwdt0_sel_clks[] = {
> + "dummy", "dummy", "pclk3_div4096", "lirc"
> +};
> +
> +static const char *const wwdt1_sel_clks[] = {
> + "dummy", "dummy", "pclk3_div4096", "lirc"
> +};
> +
> +static const char *const wwdt2_sel_clks[] = {
> + "dummy", "dummy", "pclk4_div4096", "lirc"
> +};
> +
> +static const char *const spi0_sel_clks[] = {
> + "pclk1", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const spi1_sel_clks[] = {
> + "pclk2", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const spi2_sel_clks[] = {
> + "pclk1", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const spi3_sel_clks[] = {
> + "pclk2", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const qspi0_sel_clks[] = {
> + "pclk0", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const qspi1_sel_clks[] = {
> + "pclk0", "apll", "dummy", "dummy"
> +};
> +
> +static const char *const i2s0_sel_clks[] = {
> + "apll", "sysclk1_div2", "dummy", "dummy"
> +};
> +
> +static const char *const i2s1_sel_clks[] = {
> + "apll", "sysclk1_div2", "dummy", "dummy"
> +};
> +
> +static const char *const can_sel_clks[] = {
> + "apll", "vpll"
> +};
> +
> +static const char *const cko_sel_clks[] = {
> + "hxt", "lxt", "hirc", "lirc", "capll_div4", "syspll",
> + "ddrpll", "epll_div2", "apll", "vpll", "dummy", "dummy",
> + "dummy", "dummy", "dummy", "dummy"
> +};
> +
> +static const char *const smc_sel_clks[] = {
> + "hxt", "pclk4"
> +};
> +
> +static const char *const kpi_sel_clks[] = {
> + "hxt", "lxt"
> +};
> +
> +static const struct clk_div_table ip_div_table[] = {
> + {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
> + {5, 12}, {6, 14}, {7, 16}, {0, 0},
> +};
> +
> +static const struct clk_div_table eadc_div_table[] = {
> + {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
> + {5, 12}, {6, 14}, {7, 16}, {8, 18},
> + {9, 20}, {10, 22}, {11, 24}, {12, 26},
> + {13, 28}, {14, 30}, {15, 32}, {0, 0},
> +};
> +
> +static struct clk_hw **hws;
> +static struct clk_hw_onecell_data *ma35d1_hw_data;
> +
> +static int ma35d1_clocks_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct device *dev = &pdev->dev;
> + struct device_node *clk_node = dev->of_node;
> + void __iomem *clk_base;
> + struct regmap *regmap;
> + u32 pllmode[5] = { 0, 0, 0, 0, 0 };
> + u32 pllfreq[5] = { 0, 0, 0, 0, 0 };
> +
> + dev_info(&pdev->dev, "Nuvoton MA35D1 Clock Driver\n");
> + ma35d1_hw_data = devm_kzalloc(&pdev->dev, struct_size(ma35d1_hw_data,
> + hws, CLK_MAX_IDX), GFP_KERNEL);
> +
> + if (WARN_ON(!ma35d1_hw_data))
> + return -ENOMEM;
> +
> + ma35d1_hw_data->num = CLK_MAX_IDX;
> + hws = ma35d1_hw_data->hws;
> +
> + clk_node = of_find_compatible_node(NULL, NULL, "nuvoton,ma35d1-clk");
> + clk_base = of_iomap(clk_node, 0);
> + of_node_put(clk_node);
> + if (!clk_base) {
> + pr_err("%s: could not map region\n", __func__);
> + return -ENOMEM;
> + }
> + regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
> + "nuvoton,sys");
> + if (IS_ERR(regmap))
> + pr_warn("%s: Unable to get syscon\n", __func__);
Don't print __func__ to user.
> +
> + /* clock sources */
> + hws[HXT] = ma35d1_clk_fixed("hxt", 24000000);
> + hws[HXT_GATE] = ma35d1_clk_gate("hxt_gate", "hxt",
> + clk_base + REG_CLK_PWRCTL, 0);
> + hws[LXT] = ma35d1_clk_fixed("lxt", 32768);
> + hws[LXT_GATE] = ma35d1_clk_gate("lxt_gate", "lxt",
> + clk_base + REG_CLK_PWRCTL, 1);
> + hws[HIRC] = ma35d1_clk_fixed("hirc", 12000000);
> + hws[HIRC_GATE] = ma35d1_clk_gate("hirc_gate", "hirc",
> + clk_base + REG_CLK_PWRCTL, 2);
> + hws[LIRC] = ma35d1_clk_fixed("lirc", 32000);
> + hws[LIRC_GATE] = ma35d1_clk_gate("lirc_gate", "lirc",
> + clk_base + REG_CLK_PWRCTL, 3);
> +
> + /* PLL */
> + of_property_read_u32_array(clk_node, "clock-pll-mode", pllmode,
> + ARRAY_SIZE(pllmode));
> + of_property_read_u32_array(clk_node, "assigned-clock-rates", pllfreq,
> + ARRAY_SIZE(pllfreq));
> +
> + /* SMIC PLL */
> + hws[CAPLL] = ma35d1_reg_clk_pll(MA35D1_CAPLL, pllmode[0], "capll",
> + "hxt", pllfreq[0],
> + clk_base + REG_CLK_PLL0CTL0, regmap);
> + hws[SYSPLL] = ma35d1_clk_fixed("syspll", 180000000);
> +
> + /* VSI PLL */
> + hws[DDRPLL] = ma35d1_reg_clk_pll(MA35D1_DDRPLL, pllmode[1], "ddrpll",
> + "hxt", pllfreq[1],
> + clk_base + REG_CLK_PLL2CTL0, regmap);
> + hws[APLL] = ma35d1_reg_clk_pll(MA35D1_APLL, pllmode[2], "apll", "hxt",
> + pllfreq[2], clk_base + REG_CLK_PLL3CTL0,
> + regmap);
> + hws[EPLL] = ma35d1_reg_clk_pll(MA35D1_EPLL, pllmode[3], "epll", "hxt",
> + pllfreq[3], clk_base + REG_CLK_PLL4CTL0,
> + regmap);
> + hws[VPLL] = ma35d1_reg_clk_pll(MA35D1_VPLL, pllmode[4], "vpll", "hxt",
> + pllfreq[4], clk_base + REG_CLK_PLL5CTL0,
> + regmap);
> + hws[EPLL_DIV2] = ma35d1_clk_fixed_factor("epll_div2", "epll", 1, 2);
> + hws[EPLL_DIV4] = ma35d1_clk_fixed_factor("epll_div4", "epll", 1, 4);
> + hws[EPLL_DIV8] = ma35d1_clk_fixed_factor("epll_div8", "epll", 1, 8);
> +
> + /* CA35 */
> + hws[CA35CLK_MUX] = ma35d1_clk_mux("ca35clk_mux",
> + clk_base + REG_CLK_CLKSEL0, 0,
> + 2, ca35clk_sel_clks,
> + ARRAY_SIZE(ca35clk_sel_clks));
> +
> + /* AXI */
> + hws[AXICLK_DIV2] = ma35d1_clk_fixed_factor("capll_div2", "ca35clk_mux",
> + 1, 2);
> + hws[AXICLK_DIV4] = ma35d1_clk_fixed_factor("capll_div4", "ca35clk_mux",
> + 1, 4);
> + hws[AXICLK_MUX] = ma35d1_clk_mux("axiclk_mux",
> + clk_base + REG_CLK_CLKDIV0,
> + 26, 1, axiclk_sel_clks,
> + ARRAY_SIZE(axiclk_sel_clks));
> +
> + /* SYSCLK0 & SYSCLK1 */
> + hws[SYSCLK0_MUX] = ma35d1_clk_mux("sysclk0_mux",
> + clk_base + REG_CLK_CLKSEL0,
> + 2, 1, sysclk0_sel_clks,
> + ARRAY_SIZE(sysclk0_sel_clks));
> + hws[SYSCLK1_MUX] = ma35d1_clk_mux("sysclk1_mux",
> + clk_base + REG_CLK_CLKSEL0,
> + 4, 1, sysclk1_sel_clks,
> + ARRAY_SIZE(sysclk1_sel_clks));
> + hws[SYSCLK1_DIV2] = ma35d1_clk_fixed_factor("sysclk1_div2",
> + "sysclk1_mux", 1, 2);
> +
> + /* HCLK0~3 & PCLK0~4 */
> + hws[HCLK0] = ma35d1_clk_fixed_factor("hclk0", "sysclk1_mux", 1, 1);
> + hws[HCLK1] = ma35d1_clk_fixed_factor("hclk1", "sysclk1_mux", 1, 1);
> + hws[HCLK2] = ma35d1_clk_fixed_factor("hclk2", "sysclk1_mux", 1, 1);
> + hws[PCLK0] = ma35d1_clk_fixed_factor("pclk0", "sysclk1_mux", 1, 1);
> + hws[PCLK1] = ma35d1_clk_fixed_factor("pclk1", "sysclk1_mux", 1, 1);
> + hws[PCLK2] = ma35d1_clk_fixed_factor("pclk2", "sysclk1_mux", 1, 1);
> +
> + hws[HCLK3] = ma35d1_clk_fixed_factor("hclk3", "sysclk1_mux", 1, 2);
> + hws[PCLK3] = ma35d1_clk_fixed_factor("pclk3", "sysclk1_mux", 1, 2);
> + hws[PCLK4] = ma35d1_clk_fixed_factor("pclk4", "sysclk1_mux", 1, 2);
> +
> + hws[USBPHY0] = ma35d1_clk_fixed("usbphy0", 480000000);
> + hws[USBPHY1] = ma35d1_clk_fixed("usbphy1", 480000000);
> +
> + /* DDR */
> + hws[DDR0_GATE] = ma35d1_clk_gate("ddr0_gate", "ddrpll",
> + clk_base + REG_CLK_SYSCLK0, 4);
> + hws[DDR6_GATE] = ma35d1_clk_gate("ddr6_gate", "ddrpll",
> + clk_base + REG_CLK_SYSCLK0, 5);
> +
> + /* CAN0 */
> + hws[CAN0_MUX] = ma35d1_clk_mux("can0_mux", clk_base + REG_CLK_CLKSEL4,
> + 16, 1, can_sel_clks,
> + ARRAY_SIZE(can_sel_clks));
> + hws[CAN0_DIV] = ma35d1_clk_divider_table("can0_div", "can0_mux",
> + clk_base + REG_CLK_CLKDIV0,
> + 0, 3, ip_div_table);
> + hws[CAN0_GATE] = ma35d1_clk_gate("can0_gate", "can0_div",
> + clk_base + REG_CLK_SYSCLK0, 8);
> +
> + /* CAN1 */
> + hws[CAN1_MUX] = ma35d1_clk_mux("can1_mux", clk_base + REG_CLK_CLKSEL4,
> + 17, 1, can_sel_clks,
> + ARRAY_SIZE(can_sel_clks));
> + hws[CAN1_DIV] = ma35d1_clk_divider_table("can1_div", "can1_mux",
> + clk_base + REG_CLK_CLKDIV0,
> + 4, 3, ip_div_table);
> + hws[CAN1_GATE] = ma35d1_clk_gate("can1_gate", "can1_div",
> + clk_base + REG_CLK_SYSCLK0, 9);
> +
> + /* CAN2 */
> + hws[CAN2_MUX] = ma35d1_clk_mux("can2_mux", clk_base + REG_CLK_CLKSEL4,
> + 18, 1, can_sel_clks,
> + ARRAY_SIZE(can_sel_clks));
> + hws[CAN2_DIV] = ma35d1_clk_divider_table("can2_div", "can2_mux",
> + clk_base + REG_CLK_CLKDIV0,
> + 8, 3, ip_div_table);
> + hws[CAN2_GATE] = ma35d1_clk_gate("can2_gate", "can2_div",
> + clk_base + REG_CLK_SYSCLK0, 10);
> +
> + /* CAN3 */
> + hws[CAN3_MUX] = ma35d1_clk_mux("can3_mux", clk_base + REG_CLK_CLKSEL4,
> + 19, 1, can_sel_clks,
> + ARRAY_SIZE(can_sel_clks));
> + hws[CAN3_DIV] = ma35d1_clk_divider_table("can3_div", "can3_mux",
> + clk_base + REG_CLK_CLKDIV0,
> + 12, 3, ip_div_table);
> + hws[CAN3_GATE] = ma35d1_clk_gate("can3_gate", "can3_div",
> + clk_base + REG_CLK_SYSCLK0, 11);
> +
> + /* SDH0 */
> + hws[SDH0_MUX] = ma35d1_clk_mux("sdh0_mux", clk_base + REG_CLK_CLKSEL0,
> + 16, 2, sdh_sel_clks,
> + ARRAY_SIZE(sdh_sel_clks));
> + hws[SDH0_GATE] = ma35d1_clk_gate("sdh0_gate", "sdh0_mux",
> + clk_base + REG_CLK_SYSCLK0, 16);
> +
> + /* SDH1 */
> + hws[SDH1_MUX] = ma35d1_clk_mux("sdh1_mux", clk_base + REG_CLK_CLKSEL0,
> + 18, 2, sdh_sel_clks,
> + ARRAY_SIZE(sdh_sel_clks));
> + hws[SDH1_GATE] = ma35d1_clk_gate("sdh1_gate", "sdh1_mux",
> + clk_base + REG_CLK_SYSCLK0, 17);
> +
> + /* NAND */
> + hws[NAND_GATE] = ma35d1_clk_gate("nand_gate", "hclk1",
> + clk_base + REG_CLK_SYSCLK0, 18);
> +
> + /* USB */
> + hws[USBD_GATE] = ma35d1_clk_gate("usbd_gate", "usbphy0",
> + clk_base + REG_CLK_SYSCLK0, 19);
> + hws[USBH_GATE] = ma35d1_clk_gate("usbh_gate", "usbphy0",
> + clk_base + REG_CLK_SYSCLK0, 20);
> + hws[HUSBH0_GATE] = ma35d1_clk_gate("husbh0_gate", "usbphy0",
> + clk_base + REG_CLK_SYSCLK0, 21);
> + hws[HUSBH1_GATE] = ma35d1_clk_gate("husbh1_gate", "usbphy0",
> + clk_base + REG_CLK_SYSCLK0, 22);
> +
> + /* GFX */
> + hws[GFX_MUX] = ma35d1_clk_mux("gfx_mux", clk_base + REG_CLK_CLKSEL0,
> + 26, 1, gfx_sel_clks,
> + ARRAY_SIZE(gfx_sel_clks));
> + hws[GFX_GATE] = ma35d1_clk_gate("gfx_gate", "gfx_mux",
> + clk_base + REG_CLK_SYSCLK0, 24);
> +
> + /* VC8K */
> + hws[VC8K_GATE] = ma35d1_clk_gate("vc8k_gate", "sysclk0_mux",
> + clk_base + REG_CLK_SYSCLK0, 25);
> +
> + /* DCU */
> + hws[DCU_MUX] = ma35d1_clk_mux("dcu_mux", clk_base + REG_CLK_CLKSEL0,
> + 24, 1, dcu_sel_clks,
> + ARRAY_SIZE(dcu_sel_clks));
> + hws[DCU_GATE] = ma35d1_clk_gate("dcu_gate", "dcu_mux",
> + clk_base + REG_CLK_SYSCLK0, 26);
> +
> + /* DCUP */
> + hws[DCUP_DIV] = ma35d1_clk_divider_table("dcup_div", "vpll",
> + clk_base + REG_CLK_CLKDIV0,
> + 16, 3, ip_div_table);
> +
> + /* EMAC0 */
> + hws[EMAC0_GATE] = ma35d1_clk_gate("emac0_gate", "epll_div2",
> + clk_base + REG_CLK_SYSCLK0, 27);
> +
> + /* EMAC1 */
> + hws[EMAC1_GATE] = ma35d1_clk_gate("emac1_gate", "epll_div2",
> + clk_base + REG_CLK_SYSCLK0, 28);
> +
> + /* CCAP0 */
> + hws[CCAP0_MUX] = ma35d1_clk_mux("ccap0_mux",
> + clk_base + REG_CLK_CLKSEL0,
> + 12, 1, ccap_sel_clks,
> + ARRAY_SIZE(ccap_sel_clks));
> + hws[CCAP0_DIV] = ma35d1_clk_divider("ccap0_div", "ccap0_mux",
> + clk_base + REG_CLK_CLKDIV1, 8, 4);
> + hws[CCAP0_GATE] = ma35d1_clk_gate("ccap0_gate", "ccap0_div",
> + clk_base + REG_CLK_SYSCLK0, 29);
> +
> + /* CCAP1 */
> + hws[CCAP1_MUX] = ma35d1_clk_mux("ccap1_mux",
> + clk_base + REG_CLK_CLKSEL0,
> + 14, 1, ccap_sel_clks,
> + ARRAY_SIZE(ccap_sel_clks));
> + hws[CCAP1_DIV] = ma35d1_clk_divider("ccap1_div", "ccap1_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 12, 4);
> + hws[CCAP1_GATE] = ma35d1_clk_gate("ccap1_gate", "ccap1_div",
> + clk_base + REG_CLK_SYSCLK0, 30);
> +
> + /* PDMA0~3 */
> + hws[PDMA0_GATE] = ma35d1_clk_gate("pdma0_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 0);
> + hws[PDMA1_GATE] = ma35d1_clk_gate("pdma1_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 1);
> + hws[PDMA2_GATE] = ma35d1_clk_gate("pdma2_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 2);
> + hws[PDMA3_GATE] = ma35d1_clk_gate("pdma3_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 3);
> +
> + /* WH0~1 */
> + hws[WH0_GATE] = ma35d1_clk_gate("wh0_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 4);
> + hws[WH1_GATE] = ma35d1_clk_gate("wh1_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 5);
> +
> + /* HWS */
> + hws[HWS_GATE] = ma35d1_clk_gate("hws_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 6);
> +
> + /* EBI */
> + hws[EBI_GATE] = ma35d1_clk_gate("ebi_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 7);
> +
> + /* SRAM0~1 */
> + hws[SRAM0_GATE] = ma35d1_clk_gate("sram0_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 8);
> + hws[SRAM1_GATE] = ma35d1_clk_gate("sram1_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 9);
> +
> + /* ROM */
> + hws[ROM_GATE] = ma35d1_clk_gate("rom_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 10);
> +
> + /* TRA */
> + hws[TRA_GATE] = ma35d1_clk_gate("tra_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 11);
> +
> + /* DBG */
> + hws[DBG_MUX] = ma35d1_clk_mux("dbg_mux", clk_base + REG_CLK_CLKSEL0,
> + 27, 1, dbg_sel_clks,
> + ARRAY_SIZE(dbg_sel_clks));
> + hws[DBG_GATE] = ma35d1_clk_gate("dbg_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 12);
> +
> + /* CLKO */
> + hws[CKO_MUX] = ma35d1_clk_mux("cko_mux", clk_base + REG_CLK_CLKSEL4,
> + 24, 4, cko_sel_clks,
> + ARRAY_SIZE(cko_sel_clks));
> + hws[CKO_DIV] = ma35d1_clk_divider_pow2("cko_div", "cko_mux",
> + clk_base + REG_CLK_CLKOCTL,
> + 0, 4);
> + hws[CKO_GATE] = ma35d1_clk_gate("cko_gate", "cko_div",
> + clk_base + REG_CLK_SYSCLK1, 13);
> +
> + /* GTMR */
> + hws[GTMR_GATE] = ma35d1_clk_gate("gtmr_gate", "hirc",
> + clk_base + REG_CLK_SYSCLK1, 14);
> +
> + /* GPIO */
> + hws[GPA_GATE] = ma35d1_clk_gate("gpa_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 16);
> + hws[GPB_GATE] = ma35d1_clk_gate("gpb_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 17);
> + hws[GPC_GATE] = ma35d1_clk_gate("gpc_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 18);
> + hws[GPD_GATE] = ma35d1_clk_gate("gpd_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 19);
> + hws[GPE_GATE] = ma35d1_clk_gate("gpe_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 20);
> + hws[GPF_GATE] = ma35d1_clk_gate("gpf_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 21);
> + hws[GPG_GATE] = ma35d1_clk_gate("gpg_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 22);
> + hws[GPH_GATE] = ma35d1_clk_gate("gph_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 23);
> + hws[GPI_GATE] = ma35d1_clk_gate("gpi_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 24);
> + hws[GPJ_GATE] = ma35d1_clk_gate("gpj_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 25);
> + hws[GPK_GATE] = ma35d1_clk_gate("gpk_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 26);
> + hws[GPL_GATE] = ma35d1_clk_gate("gpl_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 27);
> + hws[GPM_GATE] = ma35d1_clk_gate("gpm_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 28);
> + hws[GPN_GATE] = ma35d1_clk_gate("gpn_gate", "hclk0",
> + clk_base + REG_CLK_SYSCLK1, 29);
> +
> + /* TIMER0~11 */
> + hws[TMR0_MUX] = ma35d1_clk_mux("tmr0_mux", clk_base + REG_CLK_CLKSEL1,
> + 0, 3, timer0_sel_clks,
> + ARRAY_SIZE(timer0_sel_clks));
> + hws[TMR0_GATE] = ma35d1_clk_gate("tmr0_gate", "tmr0_mux",
> + clk_base + REG_CLK_APBCLK0, 0);
> + hws[TMR1_MUX] = ma35d1_clk_mux("tmr1_mux", clk_base + REG_CLK_CLKSEL1,
> + 4, 3, timer1_sel_clks,
> + ARRAY_SIZE(timer1_sel_clks));
> + hws[TMR1_GATE] = ma35d1_clk_gate("tmr1_gate", "tmr1_mux",
> + clk_base + REG_CLK_APBCLK0, 1);
> + hws[TMR2_MUX] = ma35d1_clk_mux("tmr2_mux", clk_base + REG_CLK_CLKSEL1,
> + 8, 3, timer2_sel_clks,
> + ARRAY_SIZE(timer2_sel_clks));
> + hws[TMR2_GATE] = ma35d1_clk_gate("tmr2_gate", "tmr2_mux",
> + clk_base + REG_CLK_APBCLK0, 2);
> + hws[TMR3_MUX] = ma35d1_clk_mux("tmr3_mux", clk_base + REG_CLK_CLKSEL1,
> + 12, 3, timer3_sel_clks,
> + ARRAY_SIZE(timer3_sel_clks));
> + hws[TMR3_GATE] = ma35d1_clk_gate("tmr3_gate", "tmr3_mux",
> + clk_base + REG_CLK_APBCLK0, 3);
> + hws[TMR4_MUX] = ma35d1_clk_mux("tmr4_mux", clk_base + REG_CLK_CLKSEL1,
> + 16, 3, timer4_sel_clks,
> + ARRAY_SIZE(timer4_sel_clks));
> + hws[TMR4_GATE] = ma35d1_clk_gate("tmr4_gate", "tmr4_mux",
> + clk_base + REG_CLK_APBCLK0, 4);
> + hws[TMR5_MUX] = ma35d1_clk_mux("tmr5_mux", clk_base + REG_CLK_CLKSEL1,
> + 20, 3, timer5_sel_clks,
> + ARRAY_SIZE(timer5_sel_clks));
> + hws[TMR5_GATE] = ma35d1_clk_gate("tmr5_gate", "tmr5_mux",
> + clk_base + REG_CLK_APBCLK0, 5);
> + hws[TMR6_MUX] = ma35d1_clk_mux("tmr6_mux", clk_base + REG_CLK_CLKSEL1,
> + 24, 3, timer6_sel_clks,
> + ARRAY_SIZE(timer6_sel_clks));
> + hws[TMR6_GATE] = ma35d1_clk_gate("tmr6_gate", "tmr6_mux",
> + clk_base + REG_CLK_APBCLK0, 6);
> + hws[TMR7_MUX] = ma35d1_clk_mux("tmr7_mux", clk_base + REG_CLK_CLKSEL1,
> + 28, 3, timer7_sel_clks,
> + ARRAY_SIZE(timer7_sel_clks));
> + hws[TMR7_GATE] = ma35d1_clk_gate("tmr7_gate", "tmr7_mux",
> + clk_base + REG_CLK_APBCLK0, 7);
> + hws[TMR8_MUX] = ma35d1_clk_mux("tmr8_mux", clk_base + REG_CLK_CLKSEL2,
> + 0, 3, timer8_sel_clks,
> + ARRAY_SIZE(timer8_sel_clks));
> + hws[TMR8_GATE] = ma35d1_clk_gate("tmr8_gate", "tmr8_mux",
> + clk_base + REG_CLK_APBCLK0, 8);
> + hws[TMR9_MUX] = ma35d1_clk_mux("tmr9_mux", clk_base + REG_CLK_CLKSEL2,
> + 4, 3, timer9_sel_clks,
> + ARRAY_SIZE(timer9_sel_clks));
> + hws[TMR9_GATE] = ma35d1_clk_gate("tmr9_gate", "tmr9_mux",
> + clk_base + REG_CLK_APBCLK0, 9);
> + hws[TMR10_MUX] = ma35d1_clk_mux("tmr10_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 8, 3, timer10_sel_clks,
> + ARRAY_SIZE(timer10_sel_clks));
> + hws[TMR10_GATE] = ma35d1_clk_gate("tmr10_gate", "tmr10_mux",
> + clk_base + REG_CLK_APBCLK0, 10);
> + hws[TMR11_MUX] = ma35d1_clk_mux("tmr11_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 12, 3, timer11_sel_clks,
> + ARRAY_SIZE(timer11_sel_clks));
> + hws[TMR11_GATE] = ma35d1_clk_gate("tmr11_gate", "tmr11_mux",
> + clk_base + REG_CLK_APBCLK0, 11);
> +
> + /* UART0~16 */
> + hws[UART0_MUX] = ma35d1_clk_mux("uart0_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 16, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART0_DIV] = ma35d1_clk_divider("uart0_div", "uart0_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 16, 4);
> + hws[UART0_GATE] = ma35d1_clk_gate("uart0_gate", "uart0_div",
> + clk_base + REG_CLK_APBCLK0, 12);
> + hws[UART1_MUX] = ma35d1_clk_mux("uart1_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 18, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART1_DIV] = ma35d1_clk_divider("uart1_div", "uart1_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 20, 4);
> + hws[UART1_GATE] = ma35d1_clk_gate("uart1_gate", "uart1_div",
> + clk_base + REG_CLK_APBCLK0, 13);
> + hws[UART2_MUX] = ma35d1_clk_mux("uart2_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 20, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART2_DIV] = ma35d1_clk_divider("uart2_div", "uart2_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 24, 4);
> + hws[UART2_GATE] = ma35d1_clk_gate("uart2_gate", "uart2_div",
> + clk_base + REG_CLK_APBCLK0, 14);
> + hws[UART3_MUX] = ma35d1_clk_mux("uart3_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 22, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART3_DIV] = ma35d1_clk_divider("uart3_div", "uart3_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 28, 4);
> + hws[UART3_GATE] = ma35d1_clk_gate("uart3_gate", "uart3_div",
> + clk_base + REG_CLK_APBCLK0, 15);
> + hws[UART4_MUX] = ma35d1_clk_mux("uart4_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 24, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART4_DIV] = ma35d1_clk_divider("uart4_div", "uart4_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 0, 4);
> + hws[UART4_GATE] = ma35d1_clk_gate("uart4_gate", "uart4_div",
> + clk_base + REG_CLK_APBCLK0, 16);
> + hws[UART5_MUX] = ma35d1_clk_mux("uart5_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 26, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART5_DIV] = ma35d1_clk_divider("uart5_div", "uart5_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 4, 4);
> + hws[UART5_GATE] = ma35d1_clk_gate("uart5_gate", "uart5_div",
> + clk_base + REG_CLK_APBCLK0, 17);
> + hws[UART6_MUX] = ma35d1_clk_mux("uart6_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 28, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART6_DIV] = ma35d1_clk_divider("uart6_div", "uart6_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 8, 4);
> + hws[UART6_GATE] = ma35d1_clk_gate("uart6_gate", "uart6_div",
> + clk_base + REG_CLK_APBCLK0, 18);
> + hws[UART7_MUX] = ma35d1_clk_mux("uart7_mux",
> + clk_base + REG_CLK_CLKSEL2,
> + 30, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART7_DIV] = ma35d1_clk_divider("uart7_div", "uart7_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 12, 4);
> + hws[UART7_GATE] = ma35d1_clk_gate("uart7_gate", "uart7_div",
> + clk_base + REG_CLK_APBCLK0, 19);
> + hws[UART8_MUX] = ma35d1_clk_mux("uart8_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 0, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART8_DIV] = ma35d1_clk_divider("uart8_div", "uart8_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 16, 4);
> + hws[UART8_GATE] = ma35d1_clk_gate("uart8_gate", "uart8_div",
> + clk_base + REG_CLK_APBCLK0, 20);
> + hws[UART9_MUX] = ma35d1_clk_mux("uart9_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 2, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART9_DIV] = ma35d1_clk_divider("uart9_div", "uart9_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 20, 4);
> + hws[UART9_GATE] = ma35d1_clk_gate("uart9_gate", "uart9_div",
> + clk_base + REG_CLK_APBCLK0, 21);
> + hws[UART10_MUX] = ma35d1_clk_mux("uart10_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 4, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART10_DIV] = ma35d1_clk_divider("uart10_div", "uart10_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 24, 4);
> + hws[UART10_GATE] = ma35d1_clk_gate("uart10_gate", "uart10_div",
> + clk_base + REG_CLK_APBCLK0, 22);
> + hws[UART11_MUX] = ma35d1_clk_mux("uart11_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 6, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART11_DIV] = ma35d1_clk_divider("uart11_div", "uart11_mux",
> + clk_base + REG_CLK_CLKDIV2,
> + 28, 4);
> + hws[UART11_GATE] = ma35d1_clk_gate("uart11_gate", "uart11_div",
> + clk_base + REG_CLK_APBCLK0, 23);
> + hws[UART12_MUX] = ma35d1_clk_mux("uart12_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 8, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART12_DIV] = ma35d1_clk_divider("uart12_div", "uart12_mux",
> + clk_base + REG_CLK_CLKDIV3,
> + 0, 4);
> + hws[UART12_GATE] = ma35d1_clk_gate("uart12_gate", "uart12_div",
> + clk_base + REG_CLK_APBCLK0, 24);
> + hws[UART13_MUX] = ma35d1_clk_mux("uart13_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 10, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART13_DIV] = ma35d1_clk_divider("uart13_div", "uart13_mux",
> + clk_base + REG_CLK_CLKDIV3,
> + 4, 4);
> + hws[UART13_GATE] = ma35d1_clk_gate("uart13_gate", "uart13_div",
> + clk_base + REG_CLK_APBCLK0, 25);
> + hws[UART14_MUX] = ma35d1_clk_mux("uart14_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 12, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART14_DIV] = ma35d1_clk_divider("uart14_div", "uart14_mux",
> + clk_base + REG_CLK_CLKDIV3,
> + 8, 4);
> + hws[UART14_GATE] = ma35d1_clk_gate("uart14_gate", "uart14_div",
> + clk_base + REG_CLK_APBCLK0, 26);
> + hws[UART15_MUX] = ma35d1_clk_mux("uart15_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 14, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART15_DIV] = ma35d1_clk_divider("uart15_div", "uart15_mux",
> + clk_base + REG_CLK_CLKDIV3,
> + 12, 4);
> + hws[UART15_GATE] = ma35d1_clk_gate("uart15_gate", "uart15_div",
> + clk_base + REG_CLK_APBCLK0, 27);
> + hws[UART16_MUX] = ma35d1_clk_mux("uart16_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 16, 2, uart_sel_clks,
> + ARRAY_SIZE(uart_sel_clks));
> + hws[UART16_DIV] = ma35d1_clk_divider("uart16_div", "uart16_mux",
> + clk_base + REG_CLK_CLKDIV3,
> + 16, 4);
> + hws[UART16_GATE] = ma35d1_clk_gate("uart16_gate", "uart16_div",
> + clk_base + REG_CLK_APBCLK0, 28);
> +
> + /* RTC */
> + hws[RTC_GATE] = ma35d1_clk_gate("rtc_gate", "lxt",
> + clk_base + REG_CLK_APBCLK0, 29);
> +
> + /* DDRP */
> + hws[DDR_GATE] = ma35d1_clk_gate("ddr_gate", "ddrpll",
> + clk_base + REG_CLK_APBCLK0, 30);
> +
> + /* KPI */
> + hws[KPI_MUX] = ma35d1_clk_mux("kpi_mux", clk_base + REG_CLK_CLKSEL4,
> + 30, 1, kpi_sel_clks,
> + ARRAY_SIZE(kpi_sel_clks));
> + hws[KPI_DIV] = ma35d1_clk_divider("kpi_div", "kpi_mux",
> + clk_base + REG_CLK_CLKDIV4,
> + 24, 8);
> + hws[KPI_GATE] = ma35d1_clk_gate("kpi_gate", "kpi_div",
> + clk_base + REG_CLK_APBCLK0, 31);
> +
> + /* I2C0~5 */
> + hws[I2C0_GATE] = ma35d1_clk_gate("i2c0_gate", "pclk0",
> + clk_base + REG_CLK_APBCLK1, 0);
> + hws[I2C1_GATE] = ma35d1_clk_gate("i2c1_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK1, 1);
> + hws[I2C2_GATE] = ma35d1_clk_gate("i2c2_gate", "pclk2",
> + clk_base + REG_CLK_APBCLK1, 2);
> + hws[I2C3_GATE] = ma35d1_clk_gate("i2c3_gate", "pclk0",
> + clk_base + REG_CLK_APBCLK1, 3);
> + hws[I2C4_GATE] = ma35d1_clk_gate("i2c4_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK1, 4);
> + hws[I2C5_GATE] = ma35d1_clk_gate("i2c5_gate", "pclk2",
> + clk_base + REG_CLK_APBCLK1, 5);
> +
> + /* QSPI0~1 */
> + hws[QSPI0_MUX] = ma35d1_clk_mux("qspi0_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 8, 2, qspi0_sel_clks,
> + ARRAY_SIZE(qspi0_sel_clks));
> + hws[QSPI0_GATE] = ma35d1_clk_gate("qspi0_gate", "qspi0_mux",
> + clk_base + REG_CLK_APBCLK1, 6);
> + hws[QSPI1_MUX] = ma35d1_clk_mux("qspi1_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 10, 2, qspi1_sel_clks,
> + ARRAY_SIZE(qspi1_sel_clks));
> + hws[QSPI1_GATE] = ma35d1_clk_gate("qspi1_gate", "qspi1_mux",
> + clk_base + REG_CLK_APBCLK1, 7);
> +
> + /* SMC0~1 */
> + hws[SMC0_MUX] = ma35d1_clk_mux("smc0_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 28, 1, smc_sel_clks,
> + ARRAY_SIZE(smc_sel_clks));
> + hws[SMC0_DIV] = ma35d1_clk_divider("smc0_div", "smc0_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 0, 4);
> + hws[SMC0_GATE] = ma35d1_clk_gate("smc0_gate", "smc0_div",
> + clk_base + REG_CLK_APBCLK1, 12);
> +
> + hws[SMC1_MUX] = ma35d1_clk_mux("smc1_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 29, 1, smc_sel_clks,
> + ARRAY_SIZE(smc_sel_clks));
> + hws[SMC1_DIV] = ma35d1_clk_divider("smc1_div", "smc1_mux",
> + clk_base + REG_CLK_CLKDIV1,
> + 4, 4);
> + hws[SMC1_GATE] = ma35d1_clk_gate("smc1_gate", "smc1_div",
> + clk_base + REG_CLK_APBCLK1, 13);
> +
> + /* WDT0~2 */
> + hws[WDT0_MUX] = ma35d1_clk_mux("wdt0_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 20, 2, wdt0_sel_clks,
> + ARRAY_SIZE(wdt0_sel_clks));
> + hws[WDT0_GATE] = ma35d1_clk_gate("wdt0_gate", "wdt0_mux",
> + clk_base + REG_CLK_APBCLK1, 16);
> + hws[WDT1_MUX] = ma35d1_clk_mux("wdt1_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 24, 2, wdt1_sel_clks,
> + ARRAY_SIZE(wdt1_sel_clks));
> + hws[WDT1_GATE] = ma35d1_clk_gate("wdt1_gate", "wdt1_mux",
> + clk_base + REG_CLK_APBCLK1, 17);
> + hws[WDT2_MUX] = ma35d1_clk_mux("wdt2_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 28, 2, wdt2_sel_clks,
> + ARRAY_SIZE(wdt2_sel_clks));
> + hws[WDT2_GATE] = ma35d1_clk_gate("wdt2_gate", "wdt2_mux",
> + clk_base + REG_CLK_APBCLK1, 18);
> +
> + /* WWDT0~2 */
> + hws[WWDT0_MUX] = ma35d1_clk_mux("wwdt0_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 22, 2, wwdt0_sel_clks,
> + ARRAY_SIZE(wwdt0_sel_clks));
> + hws[WWDT1_MUX] = ma35d1_clk_mux("wwdt1_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 26, 2, wwdt1_sel_clks,
> + ARRAY_SIZE(wwdt1_sel_clks));
> + hws[WWDT2_MUX] = ma35d1_clk_mux("wwdt2_mux",
> + clk_base + REG_CLK_CLKSEL3,
> + 30, 2, wwdt2_sel_clks,
> + ARRAY_SIZE(wwdt2_sel_clks));
> +
> + /* EPWM0~2 */
> + hws[EPWM0_GATE] = ma35d1_clk_gate("epwm0_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK1, 24);
> + hws[EPWM1_GATE] = ma35d1_clk_gate("epwm1_gate", "pclk2",
> + clk_base + REG_CLK_APBCLK1, 25);
> + hws[EPWM2_GATE] = ma35d1_clk_gate("epwm2_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK1, 26);
> +
> + /* I2S0~1 */
> + hws[I2S0_MUX] = ma35d1_clk_mux("i2s0_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 12, 2, i2s0_sel_clks,
> + ARRAY_SIZE(i2s0_sel_clks));
> + hws[I2S0_GATE] = ma35d1_clk_gate("i2s0_gate", "i2s0_mux",
> + clk_base + REG_CLK_APBCLK2, 0);
> + hws[I2S1_MUX] = ma35d1_clk_mux("i2s1_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 14, 2, i2s1_sel_clks,
> + ARRAY_SIZE(i2s1_sel_clks));
> + hws[I2S1_GATE] = ma35d1_clk_gate("i2s1_gate", "i2s1_mux",
> + clk_base + REG_CLK_APBCLK2, 1);
> +
> + /* SSMCC */
> + hws[SSMCC_GATE] = ma35d1_clk_gate("ssmcc_gate", "pclk3",
> + clk_base + REG_CLK_APBCLK2, 2);
> +
> + /* SSPCC */
> + hws[SSPCC_GATE] = ma35d1_clk_gate("sspcc_gate", "pclk3",
> + clk_base + REG_CLK_APBCLK2, 3);
> +
> + /* SPI0~3 */
> + hws[SPI0_MUX] = ma35d1_clk_mux("spi0_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 0, 2, spi0_sel_clks,
> + ARRAY_SIZE(spi0_sel_clks));
> + hws[SPI0_GATE] = ma35d1_clk_gate("spi0_gate", "spi0_mux",
> + clk_base + REG_CLK_APBCLK2, 4);
> + hws[SPI1_MUX] = ma35d1_clk_mux("spi1_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 2, 2, spi1_sel_clks,
> + ARRAY_SIZE(spi1_sel_clks));
> + hws[SPI1_GATE] = ma35d1_clk_gate("spi1_gate", "spi1_mux",
> + clk_base + REG_CLK_APBCLK2, 5);
> + hws[SPI2_MUX] = ma35d1_clk_mux("spi2_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 4, 2, spi2_sel_clks,
> + ARRAY_SIZE(spi2_sel_clks));
> + hws[SPI2_GATE] = ma35d1_clk_gate("spi2_gate", "spi2_mux",
> + clk_base + REG_CLK_APBCLK2, 6);
> + hws[SPI3_MUX] = ma35d1_clk_mux("spi3_mux",
> + clk_base + REG_CLK_CLKSEL4,
> + 6, 2, spi3_sel_clks,
> + ARRAY_SIZE(spi3_sel_clks));
> + hws[SPI3_GATE] = ma35d1_clk_gate("spi3_gate", "spi3_mux",
> + clk_base + REG_CLK_APBCLK2, 7);
> +
> + /* ECAP0~2 */
> + hws[ECAP0_GATE] = ma35d1_clk_gate("ecap0_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK2, 8);
> + hws[ECAP1_GATE] = ma35d1_clk_gate("ecap1_gate", "pclk2",
> + clk_base + REG_CLK_APBCLK2, 9);
> + hws[ECAP2_GATE] = ma35d1_clk_gate("ecap2_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK2, 10);
> +
> + /* QEI0~2 */
> + hws[QEI0_GATE] = ma35d1_clk_gate("qei0_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK2, 12);
> + hws[QEI1_GATE] = ma35d1_clk_gate("qei1_gate", "pclk2",
> + clk_base + REG_CLK_APBCLK2, 13);
> + hws[QEI2_GATE] = ma35d1_clk_gate("qei2_gate", "pclk1",
> + clk_base + REG_CLK_APBCLK2, 14);
> +
> + /* ADC */
> + hws[ADC_DIV] = ma35d1_reg_adc_clkdiv(dev, "adc_div", "pclk0", 0,
> + clk_base + REG_CLK_CLKDIV4,
> + 4, 17, 0x1ffff);
> + hws[ADC_GATE] = ma35d1_clk_gate("adc_gate", "adc_div",
> + clk_base + REG_CLK_APBCLK2, 24);
> +
> + /* EADC */
> + hws[EADC_DIV] = ma35d1_clk_divider_table("eadc_div", "pclk2",
> + clk_base + REG_CLK_CLKDIV4,
> + 0, 4, eadc_div_table);
> + hws[EADC_GATE] = ma35d1_clk_gate("eadc_gate", "eadc_div",
> + clk_base + REG_CLK_APBCLK2, 25);
> +
> + ret = of_clk_add_hw_provider(clk_node, of_clk_hw_onecell_get,
> + ma35d1_hw_data);
> + if (ret < 0) {
> + dev_err(dev, "failed to register hws for MA35D1\n");
> + iounmap(clk_base);
> + }
> + return ret;
> +}
> +
> +static const struct of_device_id ma35d1_clk_of_match[] = {
> + { .compatible = "nuvoton,ma35d1-clk" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, ma35d1_clk_of_match);
> +
> +static struct platform_driver ma35d1_clk_driver = {
> + .probe = ma35d1_clocks_probe,
> + .driver = {
> + .name = "ma35d1-clk",
> + .of_match_table = ma35d1_clk_of_match,
> + },
> +};
> +
> +static int __init ma35d1_clocks_init(void)
> +{
> + return platform_driver_register(&ma35d1_clk_driver);
> +}
> +
> +postcore_initcall(ma35d1_clocks_init);
> +
> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
Space missing.
> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
> +MODULE_LICENSE("GPL v2");
"GPL" is enough.
> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
> new file mode 100644
> index 000000000000..faae5a17e425
> --- /dev/null
> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
> @@ -0,0 +1,198 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2023 Nuvoton Technology Corp.
> + * Author: Chi-Fang Li <[email protected]>
> + */
> +
> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
> +#define __DRV_CLK_NUVOTON_MA35D1_H
> +
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk-provider.h>
> +#include <linux/spinlock.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mfd/ma35d1-sys.h>
> +
> +enum ma35d1_pll_type {
> + MA35D1_CAPLL,
> + MA35D1_DDRPLL,
> + MA35D1_APLL,
> + MA35D1_EPLL,
> + MA35D1_VPLL,
> +};
> +
> +enum ma35d1_pll_mode {
> + VSIPLL_INTEGER_MODE,
> + VSIPLL_FRACTIONAL_MODE,
> + VSIPLL_SS_MODE,
> +};
> +
> +/* VSI-PLL CTL0~2 */
> +#define VSIPLL_CTL0 0x0
> +#define VSIPLL_CTL1 0x4
> +#define VSIPLL_CTL2 0x8
> +
> +/* VSI-PLL Specification limits */
> +#define VSIPLL_FREF_MAX_FREQ 200000000UL
> +#define VSIPLL_FREF_MIN_FREQ 1000000UL
> +#define VSIPLL_FREFDIVM_MAX_FREQ 40000000UL
> +#define VSIPLL_FREFDIVM_MIN_FREQ0 1000000UL
> +#define VSIPLL_FREFDIVM_MIN_FREQ1 10000000UL
> +#define VSIPLL_FCLK_MAX_FREQ 2400000000UL
> +#define VSIPLL_FCLK_MIN_FREQ 600000000UL
> +#define VSIPLL_FCLKO_MAX_FREQ 2400000000UL
> +#define VSIPLL_FCLKO_MIN_FREQ 85700000UL
> +#define VSIPLL_SPREAD_RANGE 194
> +#define VSIPLL_MODULATION_FREQ 50000
> +
> +/* Clock Control Registers Offset */
> +#define REG_CLK_PWRCTL (0x00)
Unnecessary parenthesis.
> +#define REG_CLK_SYSCLK0 (0x04)
> +#define REG_CLK_SYSCLK1 (0x08)
> +#define REG_CLK_APBCLK0 (0x0C)
> +#define REG_CLK_APBCLK1 (0x10)
> +#define REG_CLK_APBCLK2 (0x14)
> +#define REG_CLK_CLKSEL0 (0x18)
> +#define REG_CLK_CLKSEL1 (0x1C)
> +#define REG_CLK_CLKSEL2 (0x20)
> +#define REG_CLK_CLKSEL3 (0x24)
> +#define REG_CLK_CLKSEL4 (0x28)
> +#define REG_CLK_CLKDIV0 (0x2C)
> +#define REG_CLK_CLKDIV1 (0x30)
> +#define REG_CLK_CLKDIV2 (0x34)
> +#define REG_CLK_CLKDIV3 (0x38)
> +#define REG_CLK_CLKDIV4 (0x3C)
> +#define REG_CLK_CLKOCTL (0x40)
> +#define REG_CLK_STATUS (0x50)
> +#define REG_CLK_PLL0CTL0 (0x60)
> +#define REG_CLK_PLL2CTL0 (0x80)
> +#define REG_CLK_PLL2CTL1 (0x84)
> +#define REG_CLK_PLL2CTL2 (0x88)
> +#define REG_CLK_PLL3CTL0 (0x90)
> +#define REG_CLK_PLL3CTL1 (0x94)
> +#define REG_CLK_PLL3CTL2 (0x98)
> +#define REG_CLK_PLL4CTL0 (0xA0)
> +#define REG_CLK_PLL4CTL1 (0xA4)
> +#define REG_CLK_PLL4CTL2 (0xA8)
> +#define REG_CLK_PLL5CTL0 (0xB0)
> +#define REG_CLK_PLL5CTL1 (0xB4)
> +#define REG_CLK_PLL5CTL2 (0xB8)
> +#define REG_CLK_CLKDCTL (0xC0)
> +#define REG_CLK_CLKDSTS (0xC4)
> +#define REG_CLK_CDUPB (0xC8)
> +#define REG_CLK_CDLOWB (0xCC)
> +#define REG_CLK_CKFLTRCTL (0xD0)
> +#define REG_CLK_TESTCLK (0xF0)
> +#define REG_CLK_PLLCTL (0x40)
> +
> +/* Constant Definitions for Clock Controller */
> +#define SMICPLLCTL0_FBDIV_POS (0)
> +#define SMICPLLCTL0_FBDIV_MSK (0xfful << SMICPLLCTL0_FBDIV_POS)
> +#define SMICPLLCTL0_INDIV_POS (8)
> +#define SMICPLLCTL0_INDIV_MSK (0xful << SMICPLLCTL0_INDIV_POS)
> +#define SMICPLLCTL0_OUTDIV_POS (12)
> +#define SMICPLLCTL0_OUTDIV_MSK (0x3ul << SMICPLLCTL0_OUTDIV_POS)
GENMASK() + remove _POS define completely.
> +#define SMICPLLCTL0_PD_POS (16)
> +#define SMICPLLCTL0_PD_MSK (0x1ul << SMICPLLCTL0_PD_POS)
BIT() + remove _POS.
Is this really a mask or a bit? I'd remove _MSK from the name (which is
usually not that useful anyway even if it would be a multiple bit mask
for real).
> +#define SMICPLLCTL0_BP_POS (17)
> +#define SMICPLLCTL0_BP_MSK (0x1ul << SMICPLLCTL0_BP_POS)
BIT()?
> +#define VSIPLLCTL0_FBDIV_POS (0)
> +#define VSIPLLCTL0_FBDIV_MSK (0x7fful << VSIPLLCTL0_FBDIV_POS)
> +#define VSIPLLCTL0_INDIV_POS (12)
> +#define VSIPLLCTL0_INDIV_MSK (0x3ful << VSIPLLCTL0_INDIV_POS)
> +#define VSIPLLCTL0_MODE_POS (18)
> +#define VSIPLLCTL0_MODE_MSK (0x3ul << VSIPLLCTL0_MODE_POS)
> +#define VSIPLLCTL0_SSRATE_POS (20)
> +#define VSIPLLCTL0_SSRATE_MSK (0x7fful << VSIPLLCTL0_SSRATE_POS)
> +#define VSIPLLCTL1_PD_POS (0)
> +#define VSIPLLCTL1_PD_MSK (0x1ul << VSIPLLCTL1_PD_POS)
> +#define VSIPLLCTL1_BP_POS (1)
> +#define VSIPLLCTL1_BP_MSK (0x1ul << VSIPLLCTL1_BP_POS)
> +#define VSIPLLCTL1_OUTDIV_POS (4)
> +#define VSIPLLCTL1_OUTDIV_MSK (0x7ul << VSIPLLCTL1_OUTDIV_POS)
> +#define VSIPLLCTL1_FRAC_POS (8)
> +#define VSIPLLCTL1_FRAC_MSK (0xfffffful << VSIPLLCTL1_FRAC_POS)
> +#define VSIPLLCTL2_SLOPE_POS (0)
> +#define VSIPLLCTL2_SLOPE_MSK (0xfffffful << VSIPLLCTL2_SLOPE_POS)
...and more of them.
--
i.
On Thu, 16 Mar 2023, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
> > + mem: memory@80000000 {
> > + device_type = "memory";
> > + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
> > + };
> > +};
>
> In most machines, the memory size is detected by the boot loader
> and filled in the dtb in memory before starting the kernel, so
> you should not need two separate files here for the two common
> memory configurations.
>
> Since the machine is called 'som', I would assume that this is a
> module that is integrated on another board, so more commonly one
> would have a dtsi file for the som in addition to the one for the
> soc, and have all the components of the module listed in this
> file, while the dts file that includes the som.dtsi lists the
> devices on the carrier board and enables the on-chip devices
> that are connected to the outside.
It's using syscon and simple-mfd by the looks of it.
--
Lee Jones [李琼斯]
Dear Krzysztof ,
Thank you for review.
On 2023/3/16 下午 03:31, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add Nuvoton ma35d1 system registers compatible
> Missing full stop.
I will fix it.
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> Documentation/devicetree/bindings/mfd/syscon.yaml | 1 +
>> 1 file changed, 1 insertion(+)
>>
>> diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml
>> index c828c4f5e4a7..e7a3c6e1e77f 100644
>> --- a/Documentation/devicetree/bindings/mfd/syscon.yaml
>> +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml
>> @@ -57,6 +57,7 @@ properties:
>> - microchip,sparx5-cpu-syscon
>> - mstar,msc313-pmsleep
>> - nuvoton,wpcm450-shm
>> + - nuvoton,ma35d1-sys
> Wrong order
I will fix the order.
>
> Best regards,
> Krzysztof
>
Best regards,
Jacky Huang
Dear Stephen,
Thanks for your review.
On 2023/3/16 上午 06:30, Stephen Boyd wrote:
> Quoting Jacky Huang (2023-03-15 00:28:59)
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c
>> new file mode 100644
>> index 000000000000..5f4791531e47
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c
>> @@ -0,0 +1,144 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/err.h>
>> +#include <linux/spinlock.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +#define div_mask(width) ((1 << (width)) - 1)
> This is clk_div_mask()
OK, I will rename div_mask() as clk_div_mask().
>
>> +
>> +struct ma35d1_adc_clk_divider {
>> + struct clk_hw hw;
>> + void __iomem *reg;
>> + u8 shift;
>> + u8 width;
>> + u32 mask;
>> + const struct clk_div_table *table;
>> + spinlock_t *lock;
>> +};
>> +
>> +#define to_ma35d1_adc_clk_divider(_hw) \
>> + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
>> +
>> +static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw,
>> + unsigned long parent_rate)
>> +{
>> + unsigned int val;
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + val = readl_relaxed(dclk->reg) >> dclk->shift;
>> + val &= div_mask(dclk->width);
>> + val += 1;
>> + return divider_recalc_rate(hw, parent_rate, val, dclk->table,
>> + CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
>> +}
>> +
>> +static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long *prate)
>> +{
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + return divider_round_rate(hw, rate, prate, dclk->table,
>> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
>> +}
>> +
>> +static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long parent_rate)
>> +{
>> + int value;
>> + unsigned long flags = 0;
>> + u32 data;
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + value = divider_get_val(rate, parent_rate, dclk->table,
>> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
>> +
>> + if (dclk->lock)
>> + spin_lock_irqsave(dclk->lock, flags);
>> +
>> + data = readl_relaxed(dclk->reg);
>> + data &= ~(div_mask(dclk->width) << dclk->shift);
>> + data |= (value - 1) << dclk->shift;
>> + data |= dclk->mask;
>> +
>> + writel_relaxed(data, dclk->reg);
>> +
>> + if (dclk->lock)
>> + spin_unlock_irqrestore(dclk->lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct clk_ops ma35d1_adc_clkdiv_ops = {
>> + .recalc_rate = ma35d1_clkdiv_recalc_rate,
>> + .round_rate = ma35d1_clkdiv_round_rate,
>> + .set_rate = ma35d1_clkdiv_set_rate,
>> +};
>> +
>> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
>> + const char *parent_name,
>> + unsigned long flags, void __iomem *reg,
>> + u8 shift, u8 width, u32 mask_bit)
>> +{
>> + struct ma35d1_adc_clk_divider *div;
>> + struct clk_init_data init;
>> + struct clk_div_table *table;
>> + u32 max_div, min_div;
>> + struct clk_hw *hw;
>> + int ret;
>> + int i;
>> +
>> + /* allocate the divider */
> Please remove useless comment.
>
>> + div = kzalloc(sizeof(*div), GFP_KERNEL);
>> + if (!div)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + /* Init the divider table */
> Please remove useless comment.
I will remove all useless comments.
>
>> + max_div = div_mask(width) + 1;
>> + min_div = 1;
>> +
>> + table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
> Use devm_ allocations please.
OK, I will use devm_kzallloc() instead.
>> + if (!table) {
>> + kfree(div);
>> + return ERR_PTR(-ENOMEM);
>> + }
>> +
>> + for (i = 0; i < max_div; i++) {
>> + table[i].val = (min_div + i);
>> + table[i].div = 2 * table[i].val;
>> + }
>> + table[max_div].val = 0;
>> + table[max_div].div = 0;
>> +
>> + init.name = name;
>> + init.ops = &ma35d1_adc_clkdiv_ops;
>> + init.flags |= flags;
>> + init.parent_names = parent_name ? &parent_name : NULL;
>> + init.num_parents = parent_name ? 1 : 0;
>> +
>> + /* struct ma35d1_adc_clk_divider assignments */
> Please remove useless comment.
>
>> + div->reg = reg;
>> + div->shift = shift;
>> + div->width = width;
>> + div->mask = mask_bit ? BIT(mask_bit) : 0;
>> + div->lock = &ma35d1_lock;
>> + div->hw.init = &init;
>> + div->table = table;
>> +
>> + /* Register the clock */
> Please remove useless comment.
>
>> + hw = &div->hw;
>> + ret = clk_hw_register(NULL, hw);
> Use devm_clk_hw_register()
I will use devm_clk_hw_registers() instead.
>
>> + if (ret) {
>> + kfree(table);
>> + kfree(div);
>> + return ERR_PTR(ret);
>> + }
>> + return hw;
>> +}
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>> new file mode 100644
>> index 000000000000..79e724b148fa
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>> @@ -0,0 +1,534 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk.h>
> Do you need to include this header?
I remove it and compile passed. It is not used here.
I will remove it in the next version.
>
>> +#include <linux/clk-provider.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +#include <linux/bitfield.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +#define to_ma35d1_clk_pll(clk) \
>> + (container_of(clk, struct ma35d1_clk_pll, clk))
>> +
>> +#define PLL0CTL0_FBDIV_MSK GENMASK(7, 0)
>> +#define PLL0CTL0_INDIV_MSK GENMASK(11, 8)
>> +#define PLL0CTL0_OUTDIV_MSK GENMASK(13, 12)
>> +#define PLL0CTL0_PD_MSK BIT(16)
>> +#define PLL0CTL0_BP_MSK BIT(17)
>> +#define PLLXCTL0_FBDIV_MSK GENMASK(10, 0)
>> +#define PLLXCTL0_INDIV_MSK GENMASK(17, 12)
>> +#define PLLXCTL0_MODE_MSK GENMASK(19, 18)
>> +#define PLLXCTL0_SSRATE_MSK GENMASK(30, 20)
>> +#define PLLXCTL1_PD_MSK BIT(0)
>> +#define PLLXCTL1_BP_MSK BIT(1)
>> +#define PLLXCTL1_OUTDIV_MSK GENMASK(6, 4)
>> +#define PLLXCTL1_FRAC_MSK GENMASK(31, 8)
>> +#define PLLXCTL2_SLOPE_MSK GENMASK(23, 0)
>> +
>> +struct ma35d1_clk_pll {
>> + struct clk_hw hw;
>> + u8 type;
>> + u8 mode;
>> + unsigned long rate;
>> + void __iomem *ctl0_base;
>> + void __iomem *ctl1_base;
>> + void __iomem *ctl2_base;
>> + struct regmap *regmap;
>> +};
>> +
>> +struct vsipll_freq_conf_reg_tbl {
>> + unsigned long freq;
>> + u8 mode;
>> + u32 ctl0_reg;
>> + u32 ctl1_reg;
>> + u32 ctl2_reg;
>> +};
>> +
>> +static const struct vsipll_freq_conf_reg_tbl ma35d1pll_freq[] = {
>> + { 1000000000, VSIPLL_INTEGER_MODE, 0x307d, 0x10, 0 },
>> + { 884736000, VSIPLL_FRACTIONAL_MODE, 0x41024, 0xdd2f1b11, 0 },
>> + { 533000000, VSIPLL_SS_MODE, 0x12b8102c, 0x6aaaab20, 0x12317 },
>> + { }
>> +};
>> +
>> +static void CLK_UnLockReg(struct ma35d1_clk_pll *pll)
> Please don't use a mix of upper and lower case function names.
> Everything should be lower case. Maybe the name should be
>
> ma35d1_clk_pll_unlock_reg()
Sure, we will review all the macros and API to have all lower case naming.
>> +{
>> + int ret;
>> +
>> + /* Unlock PLL registers */
>> + do {
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x59);
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x16);
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x88);
>> + regmap_read(pll->regmap, REG_SYS_RLKTZNS, &ret);
>> + } while (ret == 0);
>> +}
>> +
>> +static void CLK_LockReg(struct ma35d1_clk_pll *pll)
>> +{
> ma35d1_clk_pll_lock_reg()
>
>> + /* Lock PLL registers */
> Remove these worthless comments.
>
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x0);
>> +}
>> +
>> +/* SMIC PLL for CAPLL */
>> +unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
>> + unsigned long PllSrcClk)
>> +{
>> + u32 u32M, u32N, u32P, u32OutDiv;
> Variable names should not have the type in them. 'm', 'n', 'p', 'div'
> should suffice.
OK, we review all variables naming and modify them to linux coding style.
>> + u32 val;
>> + unsigned long u64PllClk;
>> + u32 clk_div_table[] = { 1, 2, 4, 8};
>> +
>> + val = __raw_readl(pll->ctl0_base);
> Why do you need to use __raw_readl()? Just use readl() here.
OK, I will modify all __raw_real() to raw_read().
>> +
>> + u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
>> + u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
>> + u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
>> + u32OutDiv = clk_div_table[u32P];
>> +
>> + if (val & PLL0CTL0_BP_MSK) {
>> + u64PllClk = PllSrcClk;
>> + } else {
>> + u64PllClk = PllSrcClk * u32N;
>> + do_div(u64PllClk, u32M * u32OutDiv);
>> + }
> Add a newline here.
>
>> + return u64PllClk;
>> +}
>> +
>> +/* VSI-PLL: INTEGER_MODE */
> I have no idea what this means.
The PLL has 3 operation modes, and integer mode in one of them.
We will add descriptions about PLL to comments.
>
>> +unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
>> + unsigned long u64PllFreq, u32 *u32Reg)
> Again, don't put types into the variable name.
>
>> +{
>> + u32 u32TmpM, u32TmpN, u32TmpP;
>> + u32 u32RngMinN, u32RngMinM, u32RngMinP;
>> + u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
>> + u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
>> + unsigned long u64PllClk;
>> + unsigned long u64Con1, u64Con2, u64Con3;
> My eyes! Seriously, kernel style is not this way. Did checkpatch.pl pass
> on this?
We will completely rewrite this function to make it readable.
>> +
>> + u64PllClk = 0;
>> + u32Min = (u32) -1;
>> +
>> + if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
>> + (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
>> + u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
>> + u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
>> + u64PllClk = ma35d1pll_freq[0].freq;
>> + return u64PllClk;
>> + }
>> +
>> + u32RngMinM = 1UL;
>> + u32RngMaxM = 63UL;
>> + u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
>> + (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
>> + u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
>> + (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
>> +
>> + for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
>> + u64Con1 = PllSrcClk / u32TmpM;
>> + u32RngMinN = 16UL;
>> + u32RngMaxN = 2047UL;
>> + u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
>> + (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
>> + u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
>> + (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
> Is this clamp()?
>
>> +
>> + for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
>> + u32TmpN++) {
>> + u64Con2 = u64Con1 * u32TmpN;
>> + u32RngMinP = 1UL;
>> + u32RngMaxP = 7UL;
>> + u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
>> + (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
> Is this clamp()?
>
>> + u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
>> + u32RngMaxP) ?
>> + (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
>> + u32RngMaxP;
>> + for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
>> + u32TmpP++) {
>> + u64Con3 = u64Con2 / u32TmpP;
>> + if (u64Con3 > u64PllFreq)
>> + u32Tmp = u64Con3 - u64PllFreq;
>> + else
>> + u32Tmp = u64PllFreq - u64Con3;
>> +
>> + if (u32Tmp < u32Min) {
>> + u32Min = u32Tmp;
>> + u32MinM = u32TmpM;
>> + u32MinN = u32TmpN;
>> + u32MinP = u32TmpP;
>> +
>> + if (u32Min == 0UL) {
>> + u32Reg[0] = (u32MinM << 12) |
>> + (u32MinN);
>> + u32Reg[1] = (u32MinP << 4);
>> + return ((PllSrcClk * u32MinN) /
>> + (u32MinP * u32MinM));
>> + }
>> + }
>> + }
>> + }
>> + }
> It's too hard to read this code.
We will completely rewrite this function to make it readable.
And add formula descriptions to comments.
>> +
>> + u32Reg[0] = (u32MinM << 12) | (u32MinN);
> FIELD_PREP?
>
>> + u32Reg[1] = (u32MinP << 4);
> ditto?
>
>> + u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
>> + return u64PllClk;
>> +}
>> +
>> +/* VSI-PLL: FRACTIONAL_MODE */
>> +unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
>> + unsigned long u64PllFreq, u32 *u32Reg)
>> +{
>> + unsigned long u64X, u64N, u64M, u64P, u64tmp;
>> + unsigned long u64PllClk, u64FCLKO;
>> + u32 u32FRAC;
>> +
>> + if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
>> + u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
>> + u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
>> + u64PllClk = ma35d1pll_freq[1].freq;
>> + return u64PllClk;
>> + }
>> +
>> + if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
> Use a local variable for the right hand side of the comparison.
We will completely rewrite this function to make it readable.
And add formula descriptions to comments.
>> + u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
>> + ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
> Is this DIV_ROUND_UP() or something like that?
>
>> + } else {
>> + pr_err("Failed to set rate %ld\n", u64PllFreq);
>> + return 0;
>> + }
>> +
>> + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
>> + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
>> + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
>> +
>> + if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
>> + (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
>> + return 0;
>> +
> [...]
>> + break;
>> + }
>> +
>> + return pllfreq;
>> +}
>> +
>> +static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long *prate)
>> +{
>> + return rate;
> This needs to do actual math and figure out that some rate will not
> work and calculate what the rate will actually be if clk_set_rate() is
> called with 'rate'.
Got it. We will add clock rate check here.
>> +}
>> +
>> +static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val = __raw_readl(pll->ctl1_base);
>> +
>> + return (val & VSIPLLCTL1_PD_MSK) ? 0 : 1;
>> +}
>> +
>> +static int ma35d1_clk_pll_prepare(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val;
>> +
>> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
>> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
>> + return -EACCES;
>> + }
>> +
>> + CLK_UnLockReg(pll);
>> + val = __raw_readl(pll->ctl1_base);
>> + val &= ~VSIPLLCTL1_PD_MSK;
>> + __raw_writel(val, pll->ctl1_base);
>> + CLK_LockReg(pll);
>> + return 0;
>> +}
>> +
>> +static void ma35d1_clk_pll_unprepare(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val;
>> +
>> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
>> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
>> + } else {
>> + val = __raw_readl(pll->ctl1_base);
>> + val |= VSIPLLCTL1_PD_MSK;
>> + __raw_writel(val, pll->ctl1_base);
>> + }
>> +}
>> +
>> +static const struct clk_ops ma35d1_clk_pll_ops = {
>> + .is_prepared = ma35d1_clk_pll_is_prepared,
>> + .prepare = ma35d1_clk_pll_prepare,
>> + .unprepare = ma35d1_clk_pll_unprepare,
>> + .set_rate = ma35d1_clk_pll_set_rate,
>> + .recalc_rate = ma35d1_clk_pll_recalc_rate,
>> + .round_rate = ma35d1_clk_pll_round_rate,
>> +};
>> +
>> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type,
>> + u8 u8mode, const char *name,
>> + const char *parent,
>> + unsigned long targetFreq,
>> + void __iomem *base,
>> + struct regmap *regmap)
>> +{
>> + struct ma35d1_clk_pll *pll;
>> + struct clk_hw *hw;
>> + struct clk_init_data init;
>> + int ret;
>> +
>> + pll = kmalloc(sizeof(*pll), GFP_KERNEL);
>> + if (!pll)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + pll->type = type;
>> + pll->mode = u8mode;
>> + pll->rate = targetFreq;
>> + pll->ctl0_base = base + VSIPLL_CTL0;
>> + pll->ctl1_base = base + VSIPLL_CTL1;
>> + pll->ctl2_base = base + VSIPLL_CTL2;
>> + pll->regmap = regmap;
>> +
>> + init.name = name;
>> + init.flags = 0;
>> + init.parent_names = &parent;
>> + init.num_parents = 1;
>> + init.ops = &ma35d1_clk_pll_ops;
>> + pll->hw.init = &init;
>> + hw = &pll->hw;
>> +
>> + ret = clk_hw_register(NULL, hw);
>> + if (ret) {
>> + pr_err("failed to register vsi-pll clock!!!\n");
>> + kfree(pll);
>> + return ERR_PTR(ret);
>> + }
>> + return hw;
>> +}
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1.c b/drivers/clk/nuvoton/clk-ma35d1.c
>> new file mode 100644
>> index 000000000000..ac8154458b81
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1.c
>> @@ -0,0 +1,970 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk.h>
> I don't see any clk consumer usage. Please remove.
+#include <linux/clk-provider.h>
>> +#include <linux/clkdev.h>
> I don't see any clkdev usage. Please remove.
Yes, clk.h and clkdev.h are not used here. I will remove them.
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/spinlock.h>
>> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +DEFINE_SPINLOCK(ma35d1_lock);
> Why not static?
ma35d1_lock is used in clk-ma35d1-divider.c only.
I will move it form clk-ma35d1.c to clk-ma35d1-divider.c as add static.
>
>> +
>> +static const char *const ca35clk_sel_clks[] = {
>> + "hxt", "capll", "ddrpll", "dummy"
> Are these parent mappings? Please use 'struct clk_parent_data' instead
> if so.
>
>> +};
>> +
>> +static const char *const sysclk0_sel_clks[] = {
>> + "epll_div2", "syspll"
>> +};
>> +
> [...]
>> +
>> +static struct clk_hw **hws;
>> +static struct clk_hw_onecell_data *ma35d1_hw_data;
> Any reason to make these global pointers vs local pointers during probe?
We will consider about using devm_of_clk_add_hw_provider() and remove
these global pointers.
>
>> +
>> +static int ma35d1_clocks_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct device *dev = &pdev->dev;
>> + struct device_node *clk_node = dev->of_node;
>> + void __iomem *clk_base;
>> + struct regmap *regmap;
>> + u32 pllmode[5] = { 0, 0, 0, 0, 0 };
>> + u32 pllfreq[5] = { 0, 0, 0, 0, 0 };
>> +
>> + dev_info(&pdev->dev, "Nuvoton MA35D1 Clock Driver\n");
> Drop this banner message please.
OK, I will remove it.
>
>> + ma35d1_hw_data = devm_kzalloc(&pdev->dev, struct_size(ma35d1_hw_data,
>> + hws, CLK_MAX_IDX), GFP_KERNEL);
>> +
>> + if (WARN_ON(!ma35d1_hw_data))
>> + return -ENOMEM;
>> +
>> + ma35d1_hw_data->num = CLK_MAX_IDX;
>> + hws = ma35d1_hw_data->hws;
>> +
>> + clk_node = of_find_compatible_node(NULL, NULL, "nuvoton,ma35d1-clk");
>> + clk_base = of_iomap(clk_node, 0);
> Use platform_device APIs as you have a platform device here ('pdev').
OK, I will fix it.
>> + of_node_put(clk_node);
>> + if (!clk_base) {
>> + pr_err("%s: could not map region\n", __func__);
>> + return -ENOMEM;
>> + }
>> + regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
>> + "nuvoton,sys");
> Why is it a syscon?
>
>> + if (IS_ERR(regmap))
>> + pr_warn("%s: Unable to get syscon\n", __func__);
> How can we continue without the regmap?
I will make it return error here.
>
>> +
>> + /* clock sources */
>> + hws[HXT] = ma35d1_clk_fixed("hxt", 24000000);
> [...]
>> + /* EADC */
>> + hws[EADC_DIV] = ma35d1_clk_divider_table("eadc_div", "pclk2",
>> + clk_base + REG_CLK_CLKDIV4,
>> + 0, 4, eadc_div_table);
>> + hws[EADC_GATE] = ma35d1_clk_gate("eadc_gate", "eadc_div",
>> + clk_base + REG_CLK_APBCLK2, 25);
>> +
>> + ret = of_clk_add_hw_provider(clk_node, of_clk_hw_onecell_get,
> Use devm_ variant.
OK, I will fix it.
>
>> + ma35d1_hw_data);
>> + if (ret < 0) {
>> + dev_err(dev, "failed to register hws for MA35D1\n");
>> + iounmap(clk_base);
> Use devm mapping APIs to avoid unmapping on error path.
>> + }
>> + return ret;
>> +}
>> +
>> +static const struct of_device_id ma35d1_clk_of_match[] = {
>> + { .compatible = "nuvoton,ma35d1-clk" },
>> + { },
> Drop comma above so nothing can come after this.
I will remove the comma.
>
>> +};
>> +MODULE_DEVICE_TABLE(of, ma35d1_clk_of_match);
>> +
>> +static struct platform_driver ma35d1_clk_driver = {
>> + .probe = ma35d1_clocks_probe,
>> + .driver = {
>> + .name = "ma35d1-clk",
>> + .of_match_table = ma35d1_clk_of_match,
>> + },
>> +};
>> +
>> +static int __init ma35d1_clocks_init(void)
>> +{
>> + return platform_driver_register(&ma35d1_clk_driver);
>> +}
>> +
>> +postcore_initcall(ma35d1_clocks_init);
>> +
>> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
>> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
>> new file mode 100644
>> index 000000000000..faae5a17e425
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
>> @@ -0,0 +1,198 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
>> +#define __DRV_CLK_NUVOTON_MA35D1_H
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clkdev.h>
> Are these includes used?
I remove them and compile passed, so I will will remove them in the next
version.
>
>> +#include <linux/clk-provider.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/regmap.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/ma35d1-sys.h>
> These probably aren't needed to be included here. Just forward declare
> structs you need and include the headers in the C file.
Sure, we will move all #include in clk-ma35d1.h, and only include
required .h files in C file.
>> +
> [...]
>> +
>> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev,
>> + const char *name,
>> + const char *parent_name,
>> + unsigned long flags,
>> + void __iomem *reg, u8 shift,
>> + u8 width, u32 mask_bit);
>> +
>> +extern spinlock_t ma35d1_lock;
> Why?
I will remove all "extern" ma35d1_lock, as it will be static in
clk-ma35d1-divider.c.
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your advice.
On 2023/3/16 下午 03:35, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add documentation to describe nuvoton ma35d1 clock driver bindings.
> Subject: drop second/last, redundant "bindings". The "dt-bindings"
> prefix is already stating that these are bindings.
Got it, thanks.
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> .../bindings/clock/nuvoton,ma35d1-clk.yaml | 83 +++++++++++++++++++
>> 1 file changed, 83 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>> new file mode 100644
>> index 000000000000..5c2dea071b38
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/clock/nuvoton,ma35d1-clk.yaml
>> @@ -0,0 +1,83 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/clock/nuvoton,ma35d1-clk.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Nuvoton MA35D1 Clock Controller Module Binding
>> +
>> +maintainers:
>> + - Chi-Fang Li <[email protected]>
>> + - Jacky Huang <[email protected]>
>> +
>> +description: |
>> + The MA35D1 clock controller generates clocks for the whole chip,
>> + including system clocks and all peripheral clocks.
>> +
>> + See also:
>> + include/dt-bindings/clock/ma35d1-clk.h
>> +
>> +properties:
>> + compatible:
>> + items:
>> + - const: nuvoton,ma35d1-clk
>> + - const: syscon
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + "#clock-cells":
>> + const: 1
>> +
>> + clocks:
>> + maxItems: 1
>> +
>> + clock-names:
>> + const: clk_hxt
> Drop clock-names. You do not need it for one clock.
I will remove it.
>
>
>> +
>> + assigned-clocks:
>> + maxItems: 5
>> +
>> + assigned-clock-rates:
>> + maxItems: 5
> Drop both properties, you do not need them in the binding.
I will drop them.
>
>> +
>> + nuvoton,pll-mode:
>> + description:
>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>> + integer mode, 1 is for fractional mode, and 2 is for spread
>> + spectrum mode.
>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>> + maxItems: 5
>> + items:
>> + minimum: 0
>> + maximum: 2
> Why exactly this is suitable for DT?
I will use strings instead.
>
>> +
>> + nuvoton,sys:
>> + description:
>> + Phandle to the system management controller.
>> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
> Drop quotes.
>
> You need here constraints, look for existing examples.
>
I would like to modify this as:
nuvoton,sys:
description:
Use to unlock and lock some clock controller registers. The lock
control register is in system controller.
$ref: /schemas/types.yaml#/definitions/phandle-array
items:
- items:
- description: phandle to the system controller.
>
> Best regards,
> Krzysztof
>
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your advice.
On 2023/3/16 下午 03:40, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add documentation to describe nuvoton ma35d1 uart driver bindings.
> Subject: drop second/last, redundant "bindings". The "dt-bindings"
> prefix is already stating that these are bindings.
I will fix it.
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> .../serial/nuvoton,ma35d1-serial.yaml | 52 +++++++++++++++++++
>> 1 file changed, 52 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
>> new file mode 100644
>> index 000000000000..9daa2efd4734
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/serial/nuvoton,ma35d1-serial.yaml
>> @@ -0,0 +1,52 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/serial/nuvoton,ma35d1-serial.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Nuvoton MA35D1 Universal Asynchronous Receiver/Transmitter (UART)
>> +
>> +maintainers:
>> + - Min-Jen Chen <[email protected]>
>> + - Jacky Huang <[email protected]>
>> +
>> +allOf:
>> + - $ref: "serial.yaml"
> Drop quotes. Use some recent bindings as your starting point, so we do
> not have to give comments for things which were already fixed.
Got it, I will remove the quotes.
>> +
>> +properties:
>> + compatible:
>> + const: nuvoton,ma35d1-uart
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + clocks:
>> + maxItems: 1
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - interrupts
>> + - clocks
>> +
>> +unevaluatedProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>> + #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
>> +
>> + aliases {
>> + serial0 = &uart0;
>> + };
> Drop aliases.
I will fix it.
>> +
>> + uart0:serial@40700000 {
> Drop label
OK, I will remove the label.
>
> Best regards,
> Krzysztof
>
Best regards,
Jacky Huang
On 2023/3/16 下午 10:05, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> This patchset adds initial support for the Nuvoton ma35d1 SoC, including
>> initial device tree, clock driver, reset driver, and serial driver.
>>
>> This patchset cover letter is based from the initial support for Nuvoton
>> ma35d1 to keep tracking the version history.
>>
>> This patchset had been applied to Linux kernel 6.3-rc2 and tested on the
>> Nuvoton ma35d1 SOM evaluation board.
>>
>> (ma35d1 information:
>> https://www.nuvoton.com/products/microprocessors/arm-cortex-a35-mpus/)
>> MA35D1 porting on linux-5.10.y can be found at:
>> https://github.com/OpenNuvoton/MPU-Family
> Hi Jacky,
>
> Thanks a lot for your submission. I saw this presented at
> EmbeddedWorld yesterday and asked about mainline Linux
> support, but did not expect to see the patches this soon ;-)
>
> The easiest process for getting the series merged is to
> have me add it the entire series to the SoC tree after the
> individual drivers have been reviewed by the respective
> subsystem maintainers that are already on Cc here. When
> the review is complete, you can add [email protected] to Cc,
> so they show up in patchwork, or alternatively send a pull
> request for a git tree to that address. Until then, you
> can add my own email address to Cc so I can follow the
> reviews.
>
> After the initial merge, the normal method for additional
> device drivers is to have them sent for inclusion to the
> subsystem maintainers. The soc tree and [email protected] address
> is then only used for changes in arch/arm64, i.e. updates
> to the dts files, Kconfig, defconfig and MAINTAINERS,
> as well as the drivers/soc and drivers/firmware directories,
> if you have anything in there.
>
> If you have any additional questions about the process,
> feel free to also ask me.
>
> Arnd
Hi Anrd,
Thank you very much for your kind help. You explained it so well,
I have understood the process. We got a lot of suggestions for this
patchset, and there are a lot of issues to fix. When most of the
problems get solved and acknowledged by the reviewers, I will
add you and [email protected] to Cc.
Best regards,
Jacky Huang
Hi Ilpo,
On 2023/3/16 下午 09:30, Ilpo Järvinen wrote:
> On Wed, 15 Mar 2023, Jacky Huang wrote:
>
>> From: Jacky Huang <[email protected]>
>>
>> The system manager is a set of registers used for power control,
>> multi-function pin control, USB phy control, IP reset, and other
>> miscellaneous controls. It also contains some registers that
>> provide SoC information and status.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> include/linux/mfd/ma35d1-sys.h | 95 ++++++++++++++++++++++++++++++++++
>> 1 file changed, 95 insertions(+)
>> create mode 100644 include/linux/mfd/ma35d1-sys.h
>>
>> diff --git a/include/linux/mfd/ma35d1-sys.h b/include/linux/mfd/ma35d1-sys.h
>> new file mode 100644
>> index 000000000000..dcd85231125d
>> --- /dev/null
>> +++ b/include/linux/mfd/ma35d1-sys.h
>> @@ -0,0 +1,95 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technologies.
>> + * Author: Chi-Fen Li <[email protected]>
>> + *
>> + * System management control registers of MA35D1 SoC
>> + */
>> +#ifndef __LINUX_MFD_MA35D1_SYS_H
>> +#define __LINUX_MFD_MA35D1_SYS_H
>> +
>> +#define REG_SYS_PDID (0x000) /* Product and Device Identifier */
>> +#define REG_SYS_PWRONOTP (0x004) /* Power-on Setting OTP Source */
>> +#define REG_SYS_PWRONPIN (0x008) /* Power-on Setting Pin Source */
>> +#define REG_SYS_RSTSTS (0x010) /* Reset Source Active Status */
>> +#define REG_SYS_MISCRFCR (0x014) /* Miscellaneous Reset Function */
>> +#define REG_SYS_RSTDEBCTL (0x018) /* Reset Pin De-bounce Control */
>> +#define REG_SYS_LVRDCR (0x01C) /* Low Voltage Reset & Detect */
>> +#define REG_SYS_IPRST0 (0x020) /* Reset Control Register 0 */
>> +#define REG_SYS_IPRST1 (0x024) /* Reset Control Register 1 */
>> +#define REG_SYS_IPRST2 (0x028) /* Reset Control Register 2 */
>> +#define REG_SYS_IPRST3 (0x02C) /* Reset Control Register 3 */
>> +#define REG_SYS_PMUCR (0x030) /* Power Management Unit Control */
>> +#define REG_SYS_DDRCQCSR (0x034) /* DDR Q Channel Control and Status */
>> +#define REG_SYS_PMUIEN (0x038) /* PMU Interrupt Enable */
>> +#define REG_SYS_PMUSTS (0x03C) /* PMU Status */
>> +#define REG_SYS_CA35WRBADR1 (0x040) /* A35 Core 1 Warm-boot Address */
>> +#define REG_SYS_CA35WRBPAR1 (0x044) /* A35 Core 1 Warm-boot Parameter */
>> +#define REG_SYS_CA35WRBADR2 (0x048) /* A35 Core 2 Warm-boot Address */
>> +#define REG_SYS_CA35WRBPAR2 (0x04C) /* A35 Core 2 Warm-boot Parameter */
>> +#define REG_SYS_USBPMISCR (0x060) /* USB PHY Miscellaneous Control */
>> +#define REG_SYS_USBP0PCR (0x064) /* USB Port 0 PHY Control */
>> +#define REG_SYS_USBP1PCR (0x068) /* USB Port 1 PHY Control */
>> +#define REG_SYS_MISCFCR0 (0x070) /* Miscellaneous Function Control 0 */
>> +#define REG_SYS_MISCFCR1 (0x074) /* Miscellaneous Function Control 1 */
>> +#define REG_SYS_MISCIER (0x078) /* Miscellaneous Interrupt Enable */
>> +#define REG_SYS_MISCISR (0x07C) /* Miscellaneous Interrupt Status */
>> +#define REG_SYS_GPA_MFPL (0x080) /* GPIOA Multi-Function Control LSB */
>> +#define REG_SYS_GPA_MFPH (0x084) /* GPIOA Multi-Function Control MSB */
>> +#define REG_SYS_GPB_MFPL (0x088) /* GPIOB Multi-Function Control LSB */
>> +#define REG_SYS_GPB_MFPH (0x08C) /* GPIOB Multi-Function Control MSB */
>> +#define REG_SYS_GPC_MFPL (0x090) /* GPIOC Multi-Function Control LSB */
>> +#define REG_SYS_GPC_MFPH (0x094) /* GPIOC Multi-Function Control MSB */
>> +#define REG_SYS_GPD_MFPL (0x098) /* GPIOD Multi-Function Control LSB */
>> +#define REG_SYS_GPD_MFPH (0x09C) /* GPIOD Multi-Function Control MSB */
>> +#define REG_SYS_GPE_MFPL (0x0A0) /* GPIOE Multi-Function Control LSB */
>> +#define REG_SYS_GPE_MFPH (0x0A4) /* GPIOE Multi-Function Control MSB */
>> +#define REG_SYS_GPF_MFPL (0x0A8) /* GPIOF Multi-Function Control LSB */
>> +#define REG_SYS_GPF_MFPH (0x0AC) /* GPIOF Multi-Function Control MSB */
>> +#define REG_SYS_GPG_MFPL (0x0B0) /* GPIOG Multi-Function Control LSB */
>> +#define REG_SYS_GPG_MFPH (0x0B4) /* GPIOG Multi-Function Control MSB */
>> +#define REG_SYS_GPH_MFPL (0x0B8) /* GPIOH Multi-Function Control LSB */
>> +#define REG_SYS_GPH_MFPH (0x0BC) /* GPIOH Multi-Function Control MSB */
>> +#define REG_SYS_GPI_MFPL (0x0C0) /* GPIOI Multi-Function Control LSB */
>> +#define REG_SYS_GPI_MFPH (0x0C4) /* GPIOI Multi-Function Control MSB */
>> +#define REG_SYS_GPJ_MFPL (0x0C8) /* GPIOJ Multi-Function Control LSB */
>> +#define REG_SYS_GPJ_MFPH (0x0CC) /* GPIOJ Multi-Function Control MSB */
>> +#define REG_SYS_GPK_MFPL (0x0D0) /* GPIOK Multi-Function Control LSB */
>> +#define REG_SYS_GPK_MFPH (0x0D4) /* GPIOK Multi-Function Control MSB */
>> +#define REG_SYS_GPL_MFPL (0x0D8) /* GPIOL Multi-Function Control LSB */
>> +#define REG_SYS_GPL_MFPH (0x0DC) /* GPIOL Multi-Function Control MSB */
>> +#define REG_SYS_GPM_MFPL (0x0E0) /* GPIOM Multi-Function Control LSB */
>> +#define REG_SYS_GPM_MFPH (0x0E4) /* GPIOM Multi-Function Control MSB */
>> +#define REG_SYS_GPN_MFPL (0x0E8) /* GPION Multi-Function Control LSB */
>> +#define REG_SYS_GPN_MFPH (0x0EC) /* GPION Multi-Function Control MSB */
>> +#define REG_SYS_HIRCFTRIM (0x100) /* HIRC Frequency Trim Value */
>> +#define REG_SYS_TSENSRFCR (0x104) /* Temperature Sensor Control */
>> +#define REG_SYS_GMAC0MISCR (0x108) /* GMAC 0 Miscellaneous Control */
>> +#define REG_SYS_GMAC1MISCR (0x10C) /* GMAC 1 Miscellaneous Control */
>> +#define REG_SYS_MACAD0LSR (0x110) /* MAC Address 0 LSW */
>> +#define REG_SYS_MACAD0HSR (0x114) /* MAC Address 0 HSW */
>> +#define REG_SYS_MACAD1LSR (0x118) /* MAC Address 1 LSW */
>> +#define REG_SYS_MACAD1HSR (0x11C) /* MAC Address 1 HSW */
>> +#define REG_SYS_CSDBGCTL (0x120) /* CoreSight Debug Control */
>> +#define REG_SYS_GPAB_MFOS (0x140) /* GPIOA/B Output Mode Select */
>> +#define REG_SYS_GPCD_MFOS (0x144) /* GPIOC/D Output Mode Select */
>> +#define REG_SYS_GPEF_MFOS (0x148) /* GPIOE/F Output Mode Select */
>> +#define REG_SYS_GPGH_MFOS (0x14C) /* GPIOG/H Output Mode Select */
>> +#define REG_SYS_GPIJ_MFOS (0x150) /* GPIOI/J Output Mode Select */
>> +#define REG_SYS_GPKL_MFOS (0x154) /* GPIOK/L Output Mode Select */
>> +#define REG_SYS_GPMN_MFOS (0x158) /* GPIOM/N Output Mode Select */
>> +#define REG_SYS_UID0 (0x180) /* Unique Identifier Word 0 */
>> +#define REG_SYS_UID1 (0x184) /* Unique Identifier Word 1 */
>> +#define REG_SYS_UID2 (0x188) /* Unique Identifier Word 2 */
>> +#define REG_SYS_UCID0 (0x190) /* Unique Customer Identifier 0 */
>> +#define REG_SYS_UCID1 (0x194) /* Unique Customer Identifier 1 */
>> +#define REG_SYS_UCID2 (0x198) /* Unique Customer Identifier 2 */
>> +#define REG_SYS_RLKTZS (0x1A0) /* TZS Register Lock Control */
>> +#define REG_SYS_RLKTZNS (0x1A4) /* TZNS Register Lock Control */
>> +#define REG_SYS_RLKSUBM (0x1A8) /* SubM Register Lock Control */
>> +#define REG_SYS_DPLPASWD (0x1B0) /* Deployed Password */
> Remove the extra set of parenthesis from all those above. Hex numbers are
> easier to read with lowercased letters so please convert them all to
> lowercase.
>
Got it. I will remove parenthesis and use lower case hex instead.
Best regards,
Jacky Huang
Hi Krzysztof,
On 2023/3/16 下午 03:51, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:29, Jacky Huang wrote:
>> From: Jacky Huang<[email protected]>
>>
>> This driver supports individual IP reset for ma35d1. The reset
>> control registers is a subset of system control registers.
>>
>> Signed-off-by: Jacky Huang<[email protected]>
>> ---
>> drivers/reset/Kconfig | 6 ++
>> drivers/reset/Makefile | 1 +
>> drivers/reset/reset-ma35d1.c | 152 +++++++++++++++++++++++++++++++++++
>> 3 files changed, 159 insertions(+)
>> create mode 100644 drivers/reset/reset-ma35d1.c
>>
>> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
>> index 2a52c990d4fe..47671060d259 100644
>> --- a/drivers/reset/Kconfig
>> +++ b/drivers/reset/Kconfig
>> @@ -143,6 +143,12 @@ config RESET_NPCM
>> This enables the reset controller driver for Nuvoton NPCM
>> BMC SoCs.
>>
>> +config RESET_NUVOTON_MA35D1
>> + bool "Nuvton MA35D1 Reset Driver"
>> + default ARCH_NUVOTON
> || COMPILE_TEST
I will add this config. Thank you.
>> + help
>> + This enables the reset controller driver for Nuvoton MA35D1 SoC.
>> +
> Best regards,
> Krzysztof
>
Best regards,
Jacky Huang
Hi Anrd,
Thanks for your advice.
On 2023/3/16 下午 10:23, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Enable basic drivers for ma35d1 booting up support: architecture,
>> device tree, clock, reset, and uart.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
> The description does not seem to match the actual contents,
> which only enable the platform but none of the drivers.
I would like to rewrite it as:
arm64: defconfig: Add support for Nuvoton MA35 family SoCs
This adds support for the Nuvoton MA35 family SoCs which
are based on the Cortex-A35 Armv8-A 64-bit architecture.
> It's ok generally to enable options in the defconfig file
> before you add the drivers, but if it's all part of a patch
> series, I would probable more this bit to the end.
>
> Arnd
Best regards,
Jacky Huang
On 17/03/2023 04:47, Jacky Huang wrote:
>
>>
>>> +
>>> + nuvoton,pll-mode:
>>> + description:
>>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>>> + integer mode, 1 is for fractional mode, and 2 is for spread
>>> + spectrum mode.
>>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>>> + maxItems: 5
>>> + items:
>>> + minimum: 0
>>> + maximum: 2
>> Why exactly this is suitable for DT?
>
> I will use strings instead.
I have doubts why PLL mode is a property of DT. Is this a board-specific
property?
>
>>
>>> +
>>> + nuvoton,sys:
>>> + description:
>>> + Phandle to the system management controller.
>>> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
>> Drop quotes.
>>
>> You need here constraints, look for existing examples.
>>
>
> I would like to modify this as:
>
>
> nuvoton,sys:
> description:
> Use to unlock and lock some clock controller registers. The lock
> control register is in system controller.
> $ref: /schemas/types.yaml#/definitions/phandle-array
> items:
> - items:
> - description: phandle to the system controller.
In such case you do not have array. Just make it phandle and drop the items.
Best regards,
Krzysztof
Dear Arnd,
Thanks for your advice.
On 2023/3/16 下午 10:44, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> The system manager is a set of registers used for power control,
>> multi-function pin control, USB phy control, IP reset, and other
>> miscellaneous controls. It also contains some registers that
>> provide SoC information and status.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> include/linux/mfd/ma35d1-sys.h | 95 ++++++++++++++++++++++++++++++++++
>> 1 file changed, 95 insertions(+)
>> create mode 100644 include/linux/mfd/ma35d1-sys.h
>>
>> diff --git a/include/linux/mfd/ma35d1-sys.h b/include/linux/mfd/ma35d1-sys.h
>> new file mode 100644
>> index 000000000000..dcd85231125d
>> --- /dev/null
>> +++ b/include/linux/mfd/ma35d1-sys.h
>> +
>> +#define REG_SYS_PDID (0x000) /* Product and Device Identifier */
>> +#define REG_SYS_PWRONOTP (0x004) /* Power-on Setting OTP Source */
>> +#define REG_SYS_PWRONPIN (0x008) /* Power-on Setting Pin Source */
>> +#define REG_SYS_RSTSTS (0x010) /* Reset Source Active Status */
> ...
>
> It is a bit odd to have a header file in include/linux/mfd/
> but only have the register numbers in there, and not an
> actual drivers/mfd/ driver to go along with them.
>
> I think what we often do is to just list the individual register
> numbers in the drivers that need them and not have the central
> header at all. On the other hand, I can see it's useful to
> have this documented in one place, and we clearly don't want
> to add a driver if none is needed.
>
> Maybe Lee has a suggestion for how he'd like to handle this.
Agree with this.
We will add #define of individual system control registers to the drivers
that have to use them.
So, I will remove this patch from the patchset in the next version.
>> +void ma35d1_reg_lock(void);
>> +void ma35d1_reg_unlock(void);
> These look like they were left over from an earlier version
> of the code. Since you use the regmap framework, I think this
> will take care of the locking for you.
>
> Arnd
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your advice.
On 2023/3/17 下午 05:13, Krzysztof Kozlowski wrote:
> On 17/03/2023 04:47, Jacky Huang wrote:
>>>> +
>>>> + nuvoton,pll-mode:
>>>> + description:
>>>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>>>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>>>> + integer mode, 1 is for fractional mode, and 2 is for spread
>>>> + spectrum mode.
>>>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>>>> + maxItems: 5
>>>> + items:
>>>> + minimum: 0
>>>> + maximum: 2
>>> Why exactly this is suitable for DT?
>> I will use strings instead.
> I have doubts why PLL mode is a property of DT. Is this a board-specific
> property?
CA-PLL has mode 0 only.
DDRPLL, APLL, EPLL, and VPLL have the same PLL design that supports
integer mode, fractional mode, and spread spctrum mode. The PLL mode
is controlled by clock controller register. I think it's not board-specific.
>>>> +
>>>> + nuvoton,sys:
>>>> + description:
>>>> + Phandle to the system management controller.
>>>> + $ref: "/schemas/types.yaml#/definitions/phandle-array"
>>> Drop quotes.
>>>
>>> You need here constraints, look for existing examples.
>>>
>> I would like to modify this as:
>>
>>
>> nuvoton,sys:
>> description:
>> Use to unlock and lock some clock controller registers. The lock
>> control register is in system controller.
>> $ref: /schemas/types.yaml#/definitions/phandle-array
>> items:
>> - items:
>> - description: phandle to the system controller.
> In such case you do not have array. Just make it phandle and drop the items.
>
Thank you.
So, I will rewrite it as
nuvoton,sys:
description:
Use to unlock and lock some clock controller registers. The lock
control register is in system controller.
$ref: /schemas/types.yaml#/definitions/phandle
>
> Best regards,
> Krzysztof
>
Best regards,
Jacky Huang
On Fri, Mar 17, 2023, at 07:30, Jacky Huang wrote:
> On 2023/3/16 下午 10:05, Arnd Bergmann wrote:
>
> Thank you very much for your kind help. You explained it so well,
> I have understood the process. We got a lot of suggestions for this
> patchset, and there are a lot of issues to fix. When most of the
> problems get solved and acknowledged by the reviewers, I will
> add you and [email protected] to Cc.
Ok, sounds good. Two more clarifications from me:
1. I expect you will have to go through two or three submissions
that get more feedback before everyone is happy. Please include
my [email protected] on Cc on all the submissions, but only include
the [email protected] address when all patches have an Acked-by
or Reviewed-by from the respective subsystem maintainer.
2. I think the series looks very good at this point, and most of the
feedback was about minor details, so I am optimistic that we can
actually merge it soon.
I only now saw that you had already submitted this several times
at the beginning of last year, and this is technically 'v5'
of the series, and it would make sense to add 'v6' to the subject
next time and link back to the previous [1] and this[2] submission
on lore.kernel.org.
Arnd
[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
On 17/03/2023 10:52, Jacky Huang wrote:
> Dear Krzysztof,
>
> Thanks for your advice.
>
> On 2023/3/17 下午 05:13, Krzysztof Kozlowski wrote:
>> On 17/03/2023 04:47, Jacky Huang wrote:
>>>>> +
>>>>> + nuvoton,pll-mode:
>>>>> + description:
>>>>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>>>>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>>>>> + integer mode, 1 is for fractional mode, and 2 is for spread
>>>>> + spectrum mode.
>>>>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>>>>> + maxItems: 5
>>>>> + items:
>>>>> + minimum: 0
>>>>> + maximum: 2
>>>> Why exactly this is suitable for DT?
>>> I will use strings instead.
>> I have doubts why PLL mode is a property of DT. Is this a board-specific
>> property?
>
> CA-PLL has mode 0 only.
> DDRPLL, APLL, EPLL, and VPLL have the same PLL design that supports
> integer mode, fractional mode, and spread spctrum mode. The PLL mode
> is controlled by clock controller register. I think it's not board-specific.
You described the feature but that does not answer why this is suitable
in DT. If this is not board-specific, then it is implied by compatible,
right? Or it does not have to be in DT at all.
Best regards,
Krzysztof
On 17/03/2023 14:21, Arnd Bergmann wrote:
> I only now saw that you had already submitted this several times
> at the beginning of last year, and this is technically 'v5'
> of the series, and it would make sense to add 'v6' to the subject
> next time and link back to the previous [1] and this[2] submission
> on lore.kernel.org.
... and address previous feedback. Or at least make it clear in
changelog that you addressed it, so our review was not ignored.
Best regards,
Krzysztof
Dear Arnd and Krzysztof,
Please allow me to answer the question of Krzysztof in this mail.
On 2023/3/16 下午 10:32, Arnd Bergmann wrote:
> On Thu, Mar 16, 2023, at 08:33, Krzysztof Kozlowski wrote:
>> On 15/03/2023 08:28, Jacky Huang wrote:
>>> From: Jacky Huang <[email protected]>
>>>
>>> Add binding for ARMv8 based Nuvotn SoCs and platform boards.
>>> Add initial bindings for ma35d1 series development boards.
>>>
>>> Signed-off-by: Jacky Huang <[email protected]>
>>> ---
>>> .../devicetree/bindings/arm/nuvoton.yaml | 30 +++++++++++++++++++
>> And what is npcm for? Why it was made an directory?
>>
>> All these should be just one Nuvoton.
Thank you for your suggestion, then in the next version I will submit to
rename directory npcm to nuvoton.
And rename this file nuvoton.yaml to nuvoton,ma35d1.yaml, and put in the
nuvoton directory.
> npcm is an unrelated product line, so I think it would be best
> to rename the npcm directory to nuvoton and move the new
> file in there, though I'm not sure about the name or what the
> other chips are called.
>
> My impression is that this one is more closely related to
> the older Arm9 nuc900/w90x900/n9 chips that we dropped from
> the kernel a while ago, while the npcm family has a different
> origin.
>
> Arnd
npcm focuses on the BMC field, and ma35 is the successor SoC of
nuc970/nuc980,
which is mostly used in industrial control, consumer, network
applications and other fields.
The two teams are located in different countries and regions, and there
is little
communication and resource sharing with each other.
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your advice.
On 2023/3/18 上午 12:03, Krzysztof Kozlowski wrote:
> On 17/03/2023 10:52, Jacky Huang wrote:
>> Dear Krzysztof,
>>
>> Thanks for your advice.
>>
>> On 2023/3/17 下午 05:13, Krzysztof Kozlowski wrote:
>>> On 17/03/2023 04:47, Jacky Huang wrote:
>>>>>> +
>>>>>> + nuvoton,pll-mode:
>>>>>> + description:
>>>>>> + A list of PLL operation mode corresponding to CAPLL, DDRPLL, APLL,
>>>>>> + EPLL, and VPLL in sequential. The operation mode value 0 is for
>>>>>> + integer mode, 1 is for fractional mode, and 2 is for spread
>>>>>> + spectrum mode.
>>>>>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>>>>>> + maxItems: 5
>>>>>> + items:
>>>>>> + minimum: 0
>>>>>> + maximum: 2
>>>>> Why exactly this is suitable for DT?
>>>> I will use strings instead.
>>> I have doubts why PLL mode is a property of DT. Is this a board-specific
>>> property?
>> CA-PLL has mode 0 only.
>> DDRPLL, APLL, EPLL, and VPLL have the same PLL design that supports
>> integer mode, fractional mode, and spread spctrum mode. The PLL mode
>> is controlled by clock controller register. I think it's not board-specific.
> You described the feature but that does not answer why this is suitable
> in DT. If this is not board-specific, then it is implied by compatible,
> right? Or it does not have to be in DT at all.
>
>
> Best regards,
> Krzysztof
I got it now. Yes, at least DDR PLL and VPLL (video pixel clock) can be
different on
different boards. You're right, it should be board specific. Thank you.
In the next version, I will move PLL property to board dts.
Best regards,
Jacky Huang
Dear Arnd,
Thanks for you advice.
On 2023/3/17 下午 09:21, Arnd Bergmann wrote:
> On Fri, Mar 17, 2023, at 07:30, Jacky Huang wrote:
>> On 2023/3/16 下午 10:05, Arnd Bergmann wrote:
>>
>> Thank you very much for your kind help. You explained it so well,
>> I have understood the process. We got a lot of suggestions for this
>> patchset, and there are a lot of issues to fix. When most of the
>> problems get solved and acknowledged by the reviewers, I will
>> add you and [email protected] to Cc.
> Ok, sounds good. Two more clarifications from me:
>
> 1. I expect you will have to go through two or three submissions
> that get more feedback before everyone is happy. Please include
> my [email protected] on Cc on all the submissions, but only include
> the [email protected] address when all patches have an Acked-by
> or Reviewed-by from the respective subsystem maintainer.
Sure, I will add you on Cc. Thank you.
> 2. I think the series looks very good at this point, and most of the
> feedback was about minor details, so I am optimistic that we can
> actually merge it soon.
>
> I only now saw that you had already submitted this several times
> at the beginning of last year, and this is technically 'v5'
> of the series, and it would make sense to add 'v6' to the subject
> next time and link back to the previous [1] and this[2] submission
> on lore.kernel.org.
>
>
> Arnd
>
> [1] https://lore.kernel.org/all/[email protected]/
> [2] https://lore.kernel.org/all/[email protected]/
In fact, I was thinking about this before submitting the patch. Looks
like this is causing
confusion for reviewers, so thanks for the suggestion. I will submit the
next version as v6,
and add the history of v1 ~ v4, and this version will be regarded as v5.
Best regards,
Jacky Huang
On 2023/3/18 上午 12:06, Krzysztof Kozlowski wrote:
> On 17/03/2023 14:21, Arnd Bergmann wrote:
>> I only now saw that you had already submitted this several times
>> at the beginning of last year, and this is technically 'v5'
>> of the series, and it would make sense to add 'v6' to the subject
>> next time and link back to the previous [1] and this[2] submission
>> on lore.kernel.org.
> ... and address previous feedback. Or at least make it clear in
> changelog that you addressed it, so our review was not ignored.
>
> Best regards,
> Krzysztof
Dear Krzysztof,
Thank you.
Of course, I will add back the changelog.
And, I have a question. If subsequent modifications made to a patch,
should the
"Reviewed-by" still be valid? Can we keep it?
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your advice.
On 2023/3/16 下午 03:39, Krzysztof Kozlowski wrote:
> On 16/03/2023 08:37, Krzysztof Kozlowski wrote:
>> On 15/03/2023 08:28, Jacky Huang wrote:
>>> From: Jacky Huang <[email protected]>
>>>
>>> Add documentation to describe nuvoton ma35d1 reset driver bindings.
>> Subject: drop second/last, redundant "bindings". The "dt-bindings"
>> prefix is already stating that these are bindings.
OK, I will fix it.
>>> Signed-off-by: Jacky Huang <[email protected]>
>>> ---
>>> .../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
>>> 1 file changed, 50 insertions(+)
>>> create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>> new file mode 100644
>>> index 000000000000..f66c566c6dce
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>> @@ -0,0 +1,50 @@
>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Nuvoton MA35D1 Reset Controller
>>> +
>>> +maintainers:
>>> + - Chi-Fang Li <[email protected]>
>>> + - Jacky Huang <[email protected]>
>>> +
>>> +description:
>>> + The system reset controller can be used to reset various peripheral
>>> + controllers in MA35D1 SoC.
>>> +
>>> +properties:
>>> + compatible:
>>> + const: nuvoton,ma35d1-reset
>>> +
>>> + regmap:
>>> + $ref: /schemas/types.yaml#/definitions/phandle
>>> + description: Phandle to the register map node.
>> You need to be specific what is this. As you can easily check, there is
>> no such property in any devices. I don't understand why do you need it
>> in the first place.
reset: reset-controller {
compatible = "nuvoton,ma35d1-reset";
regmap = <&sys>;
#reset-cells = <1>;
};
The dt_binding_check check report an error about the above "regmap".
I found that add this can pass the test.
>>> +
>>> + '#reset-cells':
>>> + const: 1
>>> +
>>> +required:
>>> + - compatible
>>> + - regmap
>>> + - '#reset-cells'
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> + # system reset controller node:
>>> + - |
>>> + #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
>>> +
>>> + sys: system-management@40460000 {
>>> + compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
>> And your patchset is not bisectable.... Test for bisectability before
>> sending.
> Ah, no, it's correct. I see the compatible in previous patch. You need
> to clearly describe the dependencies and merging strategy/requirements
> in cover letter.
>
> Best regards,
> Krzysztof
Sorry for the confusion.
I will add history to the cover letter.
Best regards,
Jacky Huang
Dear Krzysztof,
Thanks for your review.
On 2023/3/16 下午 03:45, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add initial device tree support for Nuvoton ma35d1 SoC, including
>> cpu, clock, reset, and serial controllers.
>> Add reference boards som-256m and iot-512m.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> arch/arm64/boot/dts/nuvoton/Makefile | 2 +
>> .../boot/dts/nuvoton/ma35d1-iot-512m.dts | 24 ++
>> .../boot/dts/nuvoton/ma35d1-som-256m.dts | 23 ++
>> arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 272 ++++++++++++++++++
>> 4 files changed, 321 insertions(+)
>> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
>> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
>> create mode 100644 arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>>
>> diff --git a/arch/arm64/boot/dts/nuvoton/Makefile b/arch/arm64/boot/dts/nuvoton/Makefile
>> index a99dab90472a..c11ab4eac9c7 100644
>> --- a/arch/arm64/boot/dts/nuvoton/Makefile
>> +++ b/arch/arm64/boot/dts/nuvoton/Makefile
>> @@ -1,2 +1,4 @@
>> # SPDX-License-Identifier: GPL-2.0
>> dtb-$(CONFIG_ARCH_NPCM) += nuvoton-npcm845-evb.dtb
>> +dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-iot-512m.dtb
>> +dtb-$(CONFIG_ARCH_NUVOTON) += ma35d1-som-256m.dtb
>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
>> new file mode 100644
>> index 000000000000..dffcaef1e6d8
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-iot-512m.dts
>> @@ -0,0 +1,24 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Shan-Chun Hung <[email protected]>
>> + * Jacky huang <[email protected]>
>> + */
>> +
>> +/dts-v1/;
>> +#include "ma35d1.dtsi"
>> +
>> +/ {
>> + model = "Nuvoton MA35D1-IoT";
>> + compatible = "nuvoton,ma35d1-iot", "nuvoton,ma35d1";
>> +
>> + chosen {
>> + stdout-path = "serial0:115200n8";
>> + };
>> +
>> + mem: memory@80000000 {
>> + device_type = "memory";
>> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
>> + };
>> +};
>> +
>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
>> new file mode 100644
>> index 000000000000..3e6c3d5469ac
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1-som-256m.dts
>> @@ -0,0 +1,23 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Shan-Chun Hung <[email protected]>
>> + * Jacky huang <[email protected]>
>> + */
>> +
>> +/dts-v1/;
>> +#include "ma35d1.dtsi"
>> +
>> +/ {
>> + model = "Nuvoton MA35D1-SOM";
>> + compatible = "nuvoton,ma35d1-som", "nuvoton,ma35d1";
>> +
>> + chosen {
>> + stdout-path = "serial0:115200n8";
>> + };
>> +
>> + mem: memory@80000000 {
>> + device_type = "memory";
>> + reg = <0x00000000 0x80000000 0 0x10000000>; /* 256M DRAM */
>> + };
>> +};
>> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>> new file mode 100644
>> index 000000000000..8c855f6b330a
>> --- /dev/null
>> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
>> @@ -0,0 +1,272 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Shan-Chun Hung <[email protected]>
>> + * Jacky huang <[email protected]>
>> + */
>> +
>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
>> +#include <dt-bindings/input/input.h>
>> +#include <dt-bindings/gpio/gpio.h>
>> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
>> +#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
>> +
>> +/ {
>> + compatible = "nuvoton,ma35d1";
>> + interrupt-parent = <&gic>;
>> + #address-cells = <2>;
>> + #size-cells = <2>;
>> +
>> + aliases {
>> + serial0 = &uart0;
>> + serial1 = &uart1;
>> + serial2 = &uart2;
>> + serial3 = &uart3;
>> + serial4 = &uart4;
>> + serial5 = &uart5;
>> + serial6 = &uart6;
>> + serial7 = &uart7;
>> + serial8 = &uart8;
>> + serial9 = &uart9;
>> + serial10 = &uart10;
>> + serial11 = &uart11;
>> + serial12 = &uart12;
>> + serial13 = &uart13;
>> + serial14 = &uart14;
>> + serial15 = &uart15;
>> + serial16 = &uart16;
> Aliases of interfaces coming out of SoC are properties of boards, not
> SoC DTSI.
OK, I will remove it from dtsi, and add to dts if required.
>> + };
>> +
>> + chosen {
>> + stdout-path = "serial0:115200n8";
>> + };
>> +
>> + cpus {
>> + #address-cells = <2>;
>> + #size-cells = <0>;
>
> Blank line.
>
>> + cpu0: cpu@0 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a35";
>> + reg = <0x0 0x0>;
>> + enable-method = "psci";
>> + next-level-cache = <&L2_0>;
>> + };
> Between every node as well.
OK, I will fix them.
>> + cpu1: cpu@1 {
>> + device_type = "cpu";
>> + compatible = "arm,cortex-a35";
>> + reg = <0x0 0x1>;
>> + enable-method = "psci";
>> + next-level-cache = <&L2_0>;
>> + };
>> + L2_0: l2-cache0 {
>> + compatible = "cache";
>> + cache-level = <2>;
>> + };
>> + };
>> +
>> + psci {
>> + compatible = "arm,psci-0.2";
>> + method = "smc";
>> + };
>> +
>> + clk_hxt: clock_hxt {
> No underscores in node names.
I will rewrite as:
clk_hxt: clock-hxt {
>
>> + compatible = "fixed-clock";
>> + #clock-cells = <0>;
>> + clock-frequency = <24000000>;
>> + clock-output-names = "clk_hxt";
> This looks like a property of boards, not SoC. Are you sure the clock
> physically is in every SoC? If so, why it is not part of clock
> controller? (before you start explaining what is this, have in mind that
> I am pretty sure I know what is this, so rather answer the questions)
Yes, it's external to SoC and should be board specific.
I will move this to board dts.
>> + };
>> +
>> + timer {
>> + compatible = "arm,armv8-timer";
>> + interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) |
>> + IRQ_TYPE_LEVEL_LOW)>, /* Physical Secure */
>> + <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) |
>> + IRQ_TYPE_LEVEL_LOW)>, /* Physical Non-Secure */
>> + <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) |
>> + IRQ_TYPE_LEVEL_LOW)>, /* Virtual */
>> + <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) |
>> + IRQ_TYPE_LEVEL_LOW)>; /* Hypervisor */
>> + clock-frequency = <12000000>;
>> + interrupt-parent = <&gic>;
>> + };
>> +
>> + sys: system-management@40460000 {
>> + compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
>> + reg = <0x0 0x40460000 0x0 0x200>;
>> +
>> + reset: reset-controller {
>> + compatible = "nuvoton,ma35d1-reset";
>> + regmap = <&sys>;
>> + #reset-cells = <1>;
>> + };
>> + };
>> +
>> + clk: clock-controller@40460200 {
>> + compatible = "nuvoton,ma35d1-clk", "syscon";
>> + reg = <0x00000000 0x40460200 0x0 0x100>;
>> + #clock-cells = <1>;
>> + clocks = <&clk_hxt>;
>> + clock-names = "clk_hxt";
>> + assigned-clocks = <&clk CAPLL>,
>> + <&clk DDRPLL>,
>> + <&clk APLL>,
>> + <&clk EPLL>,
>> + <&clk VPLL>;
>> + assigned-clock-rates = <800000000>,
>> + <266000000>,
>> + <180000000>,
>> + <500000000>,
>> + <102000000>;
>> + nuvoton,pll-mode = <0>, <1>, <0>, <0>, <0>;
>> + nuvoton,sys = <&sys>;
>> + };
>> +
>> + gic: interrupt-controller@50801000 {
>> + compatible = "arm,gic-400";
>> + #interrupt-cells = <3>;
>> + interrupt-parent = <&gic>;
>> + interrupt-controller;
>> + reg = <0x0 0x50801000 0 0x1000>, /* GICD */
>> + <0x0 0x50802000 0 0x2000>, /* GICC */
>> + <0x0 0x50804000 0 0x2000>, /* GICH */
>> + <0x0 0x50806000 0 0x2000>; /* GICV */
> reg is second property.
OK, I will fix it.
>
>> + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0x13) |
>> + IRQ_TYPE_LEVEL_HIGH)>;
>> + };
>> +
>> + uart0:serial@40700000 {
>> + compatible = "nuvoton,ma35d1-uart";
>> + reg = <0x0 0x40700000 0x0 0x100>;
>> + interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
>> + clocks = <&clk UART0_GATE>;
>> + status = "okay";
> Why? Drop the line... or convert it to disabled. Otherwise, why every
> SoC has serial0 enabled? Is it used internally?
uart0 is on all the way since this SoC booting from the MaskROM boot code,
load arm-trusted-firmware, load bootloader, and finally load linux kernel.
uart0 is also the Linux console.
>
> Best regards,
> Krzysztof
Best regards,
Jacky Huang
On Sat, Mar 18, 2023, at 04:07, Jacky Huang wrote:
> On 2023/3/18 上午 12:06, Krzysztof Kozlowski wrote:
>> On 17/03/2023 14:21, Arnd Bergmann wrote:
>>> I only now saw that you had already submitted this several times
>>> at the beginning of last year, and this is technically 'v5'
>>> of the series, and it would make sense to add 'v6' to the subject
>>> next time and link back to the previous [1] and this[2] submission
>>> on lore.kernel.org.
>> ... and address previous feedback. Or at least make it clear in
>> changelog that you addressed it, so our review was not ignored.
>>
>
> Of course, I will add back the changelog.
>
> And, I have a question. If subsequent modifications made to a patch,
> should the
>
> "Reviewed-by" still be valid? Can we keep it?
In general yes, but it's a bit of a grey area and you have
to apply common sense. Examples where I would drop the
Reviewed-by tag are
- if you changed something based on feedback from a reviewer and
they provided a Reviewed-by tag based on that changed, but then
another person asked you change the same thing differently, or
back to the original version
- if you combine a patch with another one that was not also
reviewed by the same person.
Arnd
Dear Arnd,
Thanks for your suggestion.
On 2023/3/16 下午 10:17, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>> + mem: memory@80000000 {
>> + device_type = "memory";
>> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
>> + };
>> +};
> In most machines, the memory size is detected by the boot loader
> and filled in the dtb in memory before starting the kernel, so
> you should not need two separate files here for the two common
> memory configurations.
On ma35d1, memory size is determined early before uboot.
BL1 (MaskROM boot code) -> BL2 (arm-trust-firmware) -> BL32 (op-tee) &
BL33 (uboot).
The DDR was initialized in BL2 stage with a selected DDR setting, which
is hard coded, including DDR size.
We searched the arm64 dts and found that almost all vendors claimed
memory size in board level dtsi/dts. This seems to be common.
So, can we have it unchanged?
> Since the machine is called 'som', I would assume that this is a
> module that is integrated on another board, so more commonly one
> would have a dtsi file for the som in addition to the one for the
> soc, and have all the components of the module listed in this
> file, while the dts file that includes the som.dtsi lists the
> devices on the carrier board and enables the on-chip devices
> that are connected to the outside.
>
> Arnd
You are right, ma35d1 som have a base board, and a cpu board on it.
It is a good suggestion that we should have a dtsi for som base board.
Consider that we are in the initial submit, and such a dtsi will be an empty
file at this stage. So, I would like to do it when peripheral drivers
upstream started. Is it ok?
Best regards,
Jacky Huang
Hi Lee,
Thanks for your attentions.
On 2023/3/17 上午 12:44, Lee Jones wrote:
> On Thu, 16 Mar 2023, Arnd Bergmann wrote:
>
>> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>>> + mem: memory@80000000 {
>>> + device_type = "memory";
>>> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
>>> + };
>>> +};
>> In most machines, the memory size is detected by the boot loader
>> and filled in the dtb in memory before starting the kernel, so
>> you should not need two separate files here for the two common
>> memory configurations.
>>
>> Since the machine is called 'som', I would assume that this is a
>> module that is integrated on another board, so more commonly one
>> would have a dtsi file for the som in addition to the one for the
>> soc, and have all the components of the module listed in this
>> file, while the dts file that includes the som.dtsi lists the
>> devices on the carrier board and enables the on-chip devices
>> that are connected to the outside.
> It's using syscon and simple-mfd by the looks of it.
>
> --
> Lee Jones [李琼斯]
We just copy from what other chips's dts have done.
This seems be defined in Documentation\devicetree\bindings\numa.txt.
Best regards,
Jacky Huang
On Sat, Mar 18, 2023, at 14:17, Jacky Huang wrote:
> On 2023/3/16 下午 10:17, Arnd Bergmann wrote:
>> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>>> + mem: memory@80000000 {
>>> + device_type = "memory";
>>> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
>>> + };
>>> +};
>> In most machines, the memory size is detected by the boot loader
>> and filled in the dtb in memory before starting the kernel, so
>> you should not need two separate files here for the two common
>> memory configurations.
>
>
> On ma35d1, memory size is determined early before uboot.
>
> BL1 (MaskROM boot code) -> BL2 (arm-trust-firmware) -> BL32 (op-tee) &
> BL33 (uboot).
> The DDR was initialized in BL2 stage with a selected DDR setting, which
> is hard coded, including DDR size.
>
> We searched the arm64 dts and found that almost all vendors claimed
> memory size in board level dtsi/dts. This seems to be common.
>
> So, can we have it unchanged?
I see the memory size encoded in about one out of three .dts files,
which is more than I expected. It's clearly not harmful to have it
listed in the dts, it just shouldn't be necessary.
If it helps you with your current u-boot, then leave it in, but
consider adding detection logic into u-boot so it can override
the value in the dtb file at boot time.
>> Since the machine is called 'som', I would assume that this is a
>> module that is integrated on another board, so more commonly one
>> would have a dtsi file for the som in addition to the one for the
>> soc, and have all the components of the module listed in this
>> file, while the dts file that includes the som.dtsi lists the
>> devices on the carrier board and enables the on-chip devices
>> that are connected to the outside.
>>
>
> You are right, ma35d1 som have a base board, and a cpu board on it.
>
> It is a good suggestion that we should have a dtsi for som base board.
>
> Consider that we are in the initial submit, and such a dtsi will be an empty
> file at this stage. So, I would like to do it when peripheral drivers
> upstream started. Is it ok?
It's not a big deal either way. I if you want to keep it only with
one dts file and one dtsi file, that's fine, but maybe rename the dts
file based on the name of the carrier rather than the SoM in this
case.
Arnd
Dear Krzysztof,
On 2023/3/16 下午 03:51, Krzysztof Kozlowski wrote:
> On 15/03/2023 08:28, Jacky Huang wrote:
>> From: Jacky Huang<[email protected]>
>>
>> The clock controller generates clocks for the whole chip, including
>> system clocks and all peripheral clocks. This driver support ma35d1
>> clock gating, divider, and individual PLL configuration.
>>
>> There are 6 PLLs in ma35d1 SoC:
>> - CA-PLL for the two Cortex-A35 CPU clock
>> - SYS-PLL for system bus, which comes from the companion MCU
>> and cannot be programmed by clock controller.
>> - DDR-PLL for DDR
>> - EPLL for GMAC and GFX, Display, and VDEC IPs.
>> - VPLL for video output pixel clock
>> - APLL for SDHC, I2S audio, and other IPs.
>> CA-PLL has only one operation mode.
>> DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
>> operation modes: integer mode, fraction mode, and spread specturm mode.
>>
>> Signed-off-by: Jacky Huang<[email protected]>
>> ---
>> drivers/clk/Makefile | 1 +
>> drivers/clk/nuvoton/Makefile | 4 +
>> drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 ++++
>> drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 +++++++++++++
>> drivers/clk/nuvoton/clk-ma35d1.c | 970 +++++++++++++++++++++++
>> drivers/clk/nuvoton/clk-ma35d1.h | 198 +++++
>> 6 files changed, 1851 insertions(+)
>> create mode 100644 drivers/clk/nuvoton/Makefile
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
>>
>> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
>> index e3ca0d058a25..2e7916d269e1 100644
>> --- a/drivers/clk/Makefile
>> +++ b/drivers/clk/Makefile
>> @@ -103,6 +103,7 @@ endif
>> obj-y += mstar/
>> obj-y += mvebu/
>> obj-$(CONFIG_ARCH_MXS) += mxs/
>> +obj-$(CONFIG_ARCH_NUVOTON) += nuvoton/
> Missing compile test.
>
> (...)
Thank you. We should have a Kconfig file in nuvoton directory.
I will a Kconfig file including COMPILE_TEST.
>> +
>> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
>> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
>> new file mode 100644
>> index 000000000000..faae5a17e425
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
>> @@ -0,0 +1,198 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li<[email protected]>
>> + */
>> +
>> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
>> +#define __DRV_CLK_NUVOTON_MA35D1_H
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clkdev.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/regmap.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/ma35d1-sys.h>
>> +
>> +enum ma35d1_pll_type {
>> + MA35D1_CAPLL,
>> + MA35D1_DDRPLL,
>> + MA35D1_APLL,
>> + MA35D1_EPLL,
>> + MA35D1_VPLL,
>> +};
>> +
>> +enum ma35d1_pll_mode {
>> + VSIPLL_INTEGER_MODE,
>> + VSIPLL_FRACTIONAL_MODE,
>> + VSIPLL_SS_MODE,
>> +};
>> +
>> +/* VSI-PLL CTL0~2 */
>> +#define VSIPLL_CTL0 0x0
>> +#define VSIPLL_CTL1 0x4
>> +#define VSIPLL_CTL2 0x8
>> +
>> +/* VSI-PLL Specification limits */
>> +#define VSIPLL_FREF_MAX_FREQ 200000000UL
>> +#define VSIPLL_FREF_MIN_FREQ 1000000UL
>> +#define VSIPLL_FREFDIVM_MAX_FREQ 40000000UL
>> +#define VSIPLL_FREFDIVM_MIN_FREQ0 1000000UL
>> +#define VSIPLL_FREFDIVM_MIN_FREQ1 10000000UL
>> +#define VSIPLL_FCLK_MAX_FREQ 2400000000UL
>> +#define VSIPLL_FCLK_MIN_FREQ 600000000UL
>> +#define VSIPLL_FCLKO_MAX_FREQ 2400000000UL
>> +#define VSIPLL_FCLKO_MIN_FREQ 85700000UL
>> +#define VSIPLL_SPREAD_RANGE 194
>> +#define VSIPLL_MODULATION_FREQ 50000
>> +
>> +/* Clock Control Registers Offset */
>> +#define REG_CLK_PWRCTL (0x00)
>> +#define REG_CLK_SYSCLK0 (0x04)
>> +#define REG_CLK_SYSCLK1 (0x08)
>> +#define REG_CLK_APBCLK0 (0x0C)
>> +#define REG_CLK_APBCLK1 (0x10)
>> +#define REG_CLK_APBCLK2 (0x14)
>> +#define REG_CLK_CLKSEL0 (0x18)
>> +#define REG_CLK_CLKSEL1 (0x1C)
>> +#define REG_CLK_CLKSEL2 (0x20)
>> +#define REG_CLK_CLKSEL3 (0x24)
>> +#define REG_CLK_CLKSEL4 (0x28)
>> +#define REG_CLK_CLKDIV0 (0x2C)
>> +#define REG_CLK_CLKDIV1 (0x30)
>> +#define REG_CLK_CLKDIV2 (0x34)
>> +#define REG_CLK_CLKDIV3 (0x38)
>> +#define REG_CLK_CLKDIV4 (0x3C)
>> +#define REG_CLK_CLKOCTL (0x40)
>> +#define REG_CLK_STATUS (0x50)
>> +#define REG_CLK_PLL0CTL0 (0x60)
>> +#define REG_CLK_PLL2CTL0 (0x80)
>> +#define REG_CLK_PLL2CTL1 (0x84)
>> +#define REG_CLK_PLL2CTL2 (0x88)
>> +#define REG_CLK_PLL3CTL0 (0x90)
>> +#define REG_CLK_PLL3CTL1 (0x94)
>> +#define REG_CLK_PLL3CTL2 (0x98)
>> +#define REG_CLK_PLL4CTL0 (0xA0)
>> +#define REG_CLK_PLL4CTL1 (0xA4)
>> +#define REG_CLK_PLL4CTL2 (0xA8)
>> +#define REG_CLK_PLL5CTL0 (0xB0)
>> +#define REG_CLK_PLL5CTL1 (0xB4)
>> +#define REG_CLK_PLL5CTL2 (0xB8)
>> +#define REG_CLK_CLKDCTL (0xC0)
>> +#define REG_CLK_CLKDSTS (0xC4)
>> +#define REG_CLK_CDUPB (0xC8)
>> +#define REG_CLK_CDLOWB (0xCC)
>> +#define REG_CLK_CKFLTRCTL (0xD0)
>> +#define REG_CLK_TESTCLK (0xF0)
>> +#define REG_CLK_PLLCTL (0x40)
>> +
>> +/* Constant Definitions for Clock Controller */
>> +#define SMICPLLCTL0_FBDIV_POS (0)
>> +#define SMICPLLCTL0_FBDIV_MSK (0xfful << SMICPLLCTL0_FBDIV_POS)
>> +#define SMICPLLCTL0_INDIV_POS (8)
>> +#define SMICPLLCTL0_INDIV_MSK (0xful << SMICPLLCTL0_INDIV_POS)
>> +#define SMICPLLCTL0_OUTDIV_POS (12)
>> +#define SMICPLLCTL0_OUTDIV_MSK (0x3ul << SMICPLLCTL0_OUTDIV_POS)
>> +#define SMICPLLCTL0_PD_POS (16)
>> +#define SMICPLLCTL0_PD_MSK (0x1ul << SMICPLLCTL0_PD_POS)
>> +#define SMICPLLCTL0_BP_POS (17)
>> +#define SMICPLLCTL0_BP_MSK (0x1ul << SMICPLLCTL0_BP_POS)
>> +#define VSIPLLCTL0_FBDIV_POS (0)
>> +#define VSIPLLCTL0_FBDIV_MSK (0x7fful << VSIPLLCTL0_FBDIV_POS)
>> +#define VSIPLLCTL0_INDIV_POS (12)
>> +#define VSIPLLCTL0_INDIV_MSK (0x3ful << VSIPLLCTL0_INDIV_POS)
>> +#define VSIPLLCTL0_MODE_POS (18)
>> +#define VSIPLLCTL0_MODE_MSK (0x3ul << VSIPLLCTL0_MODE_POS)
>> +#define VSIPLLCTL0_SSRATE_POS (20)
>> +#define VSIPLLCTL0_SSRATE_MSK (0x7fful << VSIPLLCTL0_SSRATE_POS)
>> +#define VSIPLLCTL1_PD_POS (0)
>> +#define VSIPLLCTL1_PD_MSK (0x1ul << VSIPLLCTL1_PD_POS)
>> +#define VSIPLLCTL1_BP_POS (1)
>> +#define VSIPLLCTL1_BP_MSK (0x1ul << VSIPLLCTL1_BP_POS)
>> +#define VSIPLLCTL1_OUTDIV_POS (4)
>> +#define VSIPLLCTL1_OUTDIV_MSK (0x7ul << VSIPLLCTL1_OUTDIV_POS)
>> +#define VSIPLLCTL1_FRAC_POS (8)
>> +#define VSIPLLCTL1_FRAC_MSK (0xfffffful << VSIPLLCTL1_FRAC_POS)
>> +#define VSIPLLCTL2_SLOPE_POS (0)
>> +#define VSIPLLCTL2_SLOPE_MSK (0xfffffful << VSIPLLCTL2_SLOPE_POS)
>> +
>> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type, u8 u8mode,
>> + const char *name, const char *parent,
>> + unsigned long targetFreq,
>> + void __iomem *base,
>> + struct regmap *regmap);
>> +
>> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev,
>> + const char *name,
>> + const char *parent_name,
>> + unsigned long flags,
>> + void __iomem *reg, u8 shift,
>> + u8 width, u32 mask_bit);
>> +
>> +extern spinlock_t ma35d1_lock;
> Why this is here?
We will remove it and use "static DEFINE_SPINLOCK(ma35d1_lock);"
and have it used in clk-ma35d1-divider.c only.
>> +
>> +static inline struct clk_hw *ma35d1_clk_fixed(const char *name, int rate)
>> +{
>> + return clk_hw_register_fixed_rate(NULL, name, NULL, 0, rate);
>> +}
>> +
> Why all these are here?
These should be static functions in clk-ma35d1.c.
We will move these function to the C file in next version.
>> +
>> +#endif /* __DRV_CLK_NUVOTON_MA35D1_H */
> Best regards,
> Krzysztof
Best regards,
Jacky Huang
On 2023/3/16 下午 11:56, Ilpo Järvinen wrote:
> On Wed, 15 Mar 2023, Jacky Huang wrote:
>
>> From: Jacky Huang <[email protected]>
>>
>> The clock controller generates clocks for the whole chip, including
>> system clocks and all peripheral clocks. This driver support ma35d1
>> clock gating, divider, and individual PLL configuration.
>>
>> There are 6 PLLs in ma35d1 SoC:
>> - CA-PLL for the two Cortex-A35 CPU clock
>> - SYS-PLL for system bus, which comes from the companion MCU
>> and cannot be programmed by clock controller.
>> - DDR-PLL for DDR
>> - EPLL for GMAC and GFX, Display, and VDEC IPs.
>> - VPLL for video output pixel clock
>> - APLL for SDHC, I2S audio, and other IPs.
>> CA-PLL has only one operation mode.
>> DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
>> operation modes: integer mode, fraction mode, and spread specturm mode.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> drivers/clk/Makefile | 1 +
>> drivers/clk/nuvoton/Makefile | 4 +
>> drivers/clk/nuvoton/clk-ma35d1-divider.c | 144 ++++
>> drivers/clk/nuvoton/clk-ma35d1-pll.c | 534 +++++++++++++
>> drivers/clk/nuvoton/clk-ma35d1.c | 970 +++++++++++++++++++++++
>> drivers/clk/nuvoton/clk-ma35d1.h | 198 +++++
>> 6 files changed, 1851 insertions(+)
>> create mode 100644 drivers/clk/nuvoton/Makefile
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-divider.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1-pll.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.c
>> create mode 100644 drivers/clk/nuvoton/clk-ma35d1.h
>>
>> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
>> index e3ca0d058a25..2e7916d269e1 100644
>> --- a/drivers/clk/Makefile
>> +++ b/drivers/clk/Makefile
>> @@ -103,6 +103,7 @@ endif
>> obj-y += mstar/
>> obj-y += mvebu/
>> obj-$(CONFIG_ARCH_MXS) += mxs/
>> +obj-$(CONFIG_ARCH_NUVOTON) += nuvoton/
>> obj-$(CONFIG_COMMON_CLK_NXP) += nxp/
>> obj-$(CONFIG_COMMON_CLK_PISTACHIO) += pistachio/
>> obj-$(CONFIG_COMMON_CLK_PXA) += pxa/
>> diff --git a/drivers/clk/nuvoton/Makefile b/drivers/clk/nuvoton/Makefile
>> new file mode 100644
>> index 000000000000..d2c092541b8d
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/Makefile
>> @@ -0,0 +1,4 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1.o
>> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-divider.o
>> +obj-$(CONFIG_ARCH_NUVOTON) += clk-ma35d1-pll.o
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1-divider.c b/drivers/clk/nuvoton/clk-ma35d1-divider.c
>> new file mode 100644
>> index 000000000000..5f4791531e47
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1-divider.c
>> @@ -0,0 +1,144 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/slab.h>
>> +#include <linux/io.h>
>> +#include <linux/err.h>
>> +#include <linux/spinlock.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +#define div_mask(width) ((1 << (width)) - 1)
>> +
>> +struct ma35d1_adc_clk_divider {
>> + struct clk_hw hw;
>> + void __iomem *reg;
>> + u8 shift;
>> + u8 width;
>> + u32 mask;
>> + const struct clk_div_table *table;
>> + spinlock_t *lock;
> Add comment to indicate what it protects.
OK, I will add comment:
/* protects concurrent access to clock divider registers */
>> +};
>> +
>> +#define to_ma35d1_adc_clk_divider(_hw) \
>> + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
> static inline
I will modify these "static" functions as "static inline".
>
>> +static unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw,
>> + unsigned long parent_rate)
>> +{
>> + unsigned int val;
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + val = readl_relaxed(dclk->reg) >> dclk->shift;
>> + val &= div_mask(dclk->width);
>> + val += 1;
>> + return divider_recalc_rate(hw, parent_rate, val, dclk->table,
>> + CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
>> +}
>> +
>> +static long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long *prate)
>> +{
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + return divider_round_rate(hw, rate, prate, dclk->table,
>> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
>> +}
>> +
>> +static int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long parent_rate)
>> +{
>> + int value;
>> + unsigned long flags = 0;
>> + u32 data;
>> + struct ma35d1_adc_clk_divider *dclk = to_ma35d1_adc_clk_divider(hw);
>> +
>> + value = divider_get_val(rate, parent_rate, dclk->table,
>> + dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
>> +
>> + if (dclk->lock)
>> + spin_lock_irqsave(dclk->lock, flags);
>> +
>> + data = readl_relaxed(dclk->reg);
>> + data &= ~(div_mask(dclk->width) << dclk->shift);
>> + data |= (value - 1) << dclk->shift;
>> + data |= dclk->mask;
>> +
>> + writel_relaxed(data, dclk->reg);
>> +
>> + if (dclk->lock)
>> + spin_unlock_irqrestore(dclk->lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct clk_ops ma35d1_adc_clkdiv_ops = {
>> + .recalc_rate = ma35d1_clkdiv_recalc_rate,
>> + .round_rate = ma35d1_clkdiv_round_rate,
>> + .set_rate = ma35d1_clkdiv_set_rate,
>> +};
>> +
>> +struct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
>> + const char *parent_name,
>> + unsigned long flags, void __iomem *reg,
>> + u8 shift, u8 width, u32 mask_bit)
>> +{
>> + struct ma35d1_adc_clk_divider *div;
>> + struct clk_init_data init;
>> + struct clk_div_table *table;
>> + u32 max_div, min_div;
>> + struct clk_hw *hw;
>> + int ret;
>> + int i;
>> +
>> + /* allocate the divider */
>> + div = kzalloc(sizeof(*div), GFP_KERNEL);
>> + if (!div)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + /* Init the divider table */
>> + max_div = div_mask(width) + 1;
>> + min_div = 1;
>> +
>> + table = kcalloc(max_div + 1, sizeof(*table), GFP_KERNEL);
>> + if (!table) {
>> + kfree(div);
>> + return ERR_PTR(-ENOMEM);
> Use rollback to do error handling:
>
> ret = ERR_PTR(-ENOMEM);
> goto free_div;
Thank you for the advice.
We will fix it in the next version.
>> + }
>> +
>> + for (i = 0; i < max_div; i++) {
>> + table[i].val = (min_div + i);
>> + table[i].div = 2 * table[i].val;
>> + }
>> + table[max_div].val = 0;
>> + table[max_div].div = 0;
>> +
>> + init.name = name;
>> + init.ops = &ma35d1_adc_clkdiv_ops;
>> + init.flags |= flags;
>> + init.parent_names = parent_name ? &parent_name : NULL;
>> + init.num_parents = parent_name ? 1 : 0;
>> +
>> + /* struct ma35d1_adc_clk_divider assignments */
>> + div->reg = reg;
>> + div->shift = shift;
>> + div->width = width;
>> + div->mask = mask_bit ? BIT(mask_bit) : 0;
>> + div->lock = &ma35d1_lock;
>> + div->hw.init = &init;
>> + div->table = table;
>> +
>> + /* Register the clock */
>> + hw = &div->hw;
>> + ret = clk_hw_register(NULL, hw);
>> + if (ret) {
>> + kfree(table);
>> + kfree(div);
>> + return ERR_PTR(ret);
> ret = ERR_PTR(ret);
> goto free_table;
>
>> + }
>> + return hw;
> free_table:
> kfree(table);
> free_div:
> kfree(div);
> return ret;
OK, we follow this.
>> +}
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>> new file mode 100644
>> index 000000000000..79e724b148fa
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>> @@ -0,0 +1,534 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +#include <linux/bitfield.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +#define to_ma35d1_clk_pll(clk) \
>> + (container_of(clk, struct ma35d1_clk_pll, clk))
> static inline
I am sorry cannot get "static inline" refer to which one.
Would you give more advice here?
Thank you.
>
>> +
>> +#define PLL0CTL0_FBDIV_MSK GENMASK(7, 0)
>> +#define PLL0CTL0_INDIV_MSK GENMASK(11, 8)
>> +#define PLL0CTL0_OUTDIV_MSK GENMASK(13, 12)
>> +#define PLL0CTL0_PD_MSK BIT(16)
>> +#define PLL0CTL0_BP_MSK BIT(17)
>> +#define PLLXCTL0_FBDIV_MSK GENMASK(10, 0)
>> +#define PLLXCTL0_INDIV_MSK GENMASK(17, 12)
>> +#define PLLXCTL0_MODE_MSK GENMASK(19, 18)
>> +#define PLLXCTL0_SSRATE_MSK GENMASK(30, 20)
>> +#define PLLXCTL1_PD_MSK BIT(0)
>> +#define PLLXCTL1_BP_MSK BIT(1)
>> +#define PLLXCTL1_OUTDIV_MSK GENMASK(6, 4)
>> +#define PLLXCTL1_FRAC_MSK GENMASK(31, 8)
>> +#define PLLXCTL2_SLOPE_MSK GENMASK(23, 0)
>> +
>> +struct ma35d1_clk_pll {
>> + struct clk_hw hw;
>> + u8 type;
>> + u8 mode;
>> + unsigned long rate;
>> + void __iomem *ctl0_base;
>> + void __iomem *ctl1_base;
>> + void __iomem *ctl2_base;
>> + struct regmap *regmap;
>> +};
>> +
>> +struct vsipll_freq_conf_reg_tbl {
>> + unsigned long freq;
>> + u8 mode;
>> + u32 ctl0_reg;
>> + u32 ctl1_reg;
>> + u32 ctl2_reg;
>> +};
>> +
>> +static const struct vsipll_freq_conf_reg_tbl ma35d1pll_freq[] = {
>> + { 1000000000, VSIPLL_INTEGER_MODE, 0x307d, 0x10, 0 },
>> + { 884736000, VSIPLL_FRACTIONAL_MODE, 0x41024, 0xdd2f1b11, 0 },
>> + { 533000000, VSIPLL_SS_MODE, 0x12b8102c, 0x6aaaab20, 0x12317 },
>> + { }
>> +};
>> +
>> +static void CLK_UnLockReg(struct ma35d1_clk_pll *pll)
> Use lowercase only.
Sure, we will fix them all.
>> +{
>> + int ret;
>> +
>> + /* Unlock PLL registers */
>> + do {
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x59);
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x16);
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x88);
>> + regmap_read(pll->regmap, REG_SYS_RLKTZNS, &ret);
>> + } while (ret == 0);
>> +}
>> +
>> +static void CLK_LockReg(struct ma35d1_clk_pll *pll)
> Ditto.
>
>> +{
>> + /* Lock PLL registers */
>> + regmap_write(pll->regmap, REG_SYS_RLKTZNS, 0x0);
>> +}
>> +
>> +/* SMIC PLL for CAPLL */
>> +unsigned long CLK_GetPLLFreq_SMICPLL(struct ma35d1_clk_pll *pll,
>> + unsigned long PllSrcClk)
> Lowercase only for function name and variable names. Please do the rest,
> I won't mention more of them.
>
>> +{
>> + u32 u32M, u32N, u32P, u32OutDiv;
>> + u32 val;
>> + unsigned long u64PllClk;
>> + u32 clk_div_table[] = { 1, 2, 4, 8};
> Inconsistent whitespaces.
will be fixed
>
>> +
>> + val = __raw_readl(pll->ctl0_base);
>> +
>> + u32N = FIELD_GET(PLL0CTL0_FBDIV_MSK, val);
>> + u32M = FIELD_GET(PLL0CTL0_INDIV_MSK, val);
>> + u32P = FIELD_GET(PLL0CTL0_OUTDIV_MSK, val);
>> + u32OutDiv = clk_div_table[u32P];
>> +
>> + if (val & PLL0CTL0_BP_MSK) {
>> + u64PllClk = PllSrcClk;
>> + } else {
>> + u64PllClk = PllSrcClk * u32N;
>> + do_div(u64PllClk, u32M * u32OutDiv);
> Does this block depend on unsigned long being 64-bit? Or should you
> enforce it by using u64 that is always same sized unlike unsigned long?
We will fix it to use u64 instead of "unsigned long".
All the naming with data type and upper case will be fixed in the next
version.
>> + }
>> + return u64PllClk;
>> +}
>> +
>> +/* VSI-PLL: INTEGER_MODE */
>> +unsigned long CLK_CalPLLFreq_Mode0(unsigned long PllSrcClk,
>> + unsigned long u64PllFreq, u32 *u32Reg)
>> +{
>> + u32 u32TmpM, u32TmpN, u32TmpP;
>> + u32 u32RngMinN, u32RngMinM, u32RngMinP;
>> + u32 u32RngMaxN, u32RngMaxM, u32RngMaxP;
>> + u32 u32Tmp, u32Min, u32MinN, u32MinM, u32MinP;
> Remove types from names.
OK, we will fix them.
>
>> + unsigned long u64PllClk;
>> + unsigned long u64Con1, u64Con2, u64Con3;
> Okay as unsigned long or do you want always 64-bit which is u64 ?
>
> Define these inside the loops below and remove the types from the name.
We will fix the "unsigned long" to be u64.
>> +
>> + u64PllClk = 0;
>> + u32Min = (u32) -1;
>> +
>> + if (!((u64PllFreq >= VSIPLL_FCLKO_MIN_FREQ) &&
>> + (u64PllFreq <= VSIPLL_FCLKO_MAX_FREQ))) {
>> + u32Reg[0] = ma35d1pll_freq[0].ctl0_reg;
>> + u32Reg[1] = ma35d1pll_freq[0].ctl1_reg;
>> + u64PllClk = ma35d1pll_freq[0].freq;
>> + return u64PllClk;
>> + }
>> +
>> + u32RngMinM = 1UL;
>> + u32RngMaxM = 63UL;
>> + u32RngMinM = ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) > 1) ?
>> + (PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) : 1;
> max(PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ, 1UL);
>
> Remember to add include for it.
>
>> + u32RngMaxM = ((PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) < u32RngMaxM) ?
>> + (PllSrcClk / VSIPLL_FREFDIVM_MIN_FREQ0) : u32RngMaxM;
> min();
OK, we will use max() and min() instead.
>> +
>> + for (u32TmpM = u32RngMinM; u32TmpM < (u32RngMaxM + 1); u32TmpM++) {
> <= and remove + 1
>
> Why can't you call this loop cariable just m ?
>
>> + u64Con1 = PllSrcClk / u32TmpM;
>> + u32RngMinN = 16UL;
>> + u32RngMaxN = 2047UL;
> Why aren't these two values in defines?
We will add #define constant for them.
>
>> + u32RngMinN = ((VSIPLL_FCLK_MIN_FREQ / u64Con1) > u32RngMinN) ?
>> + (VSIPLL_FCLK_MIN_FREQ / u64Con1) : u32RngMinN;
> max();
>
>> + u32RngMaxN = ((VSIPLL_FCLK_MAX_FREQ / u64Con1) < u32RngMaxN) ?
>> + (VSIPLL_FCLK_MAX_FREQ / u64Con1) : u32RngMaxN;
> min();
>
>> +
>> + for (u32TmpN = u32RngMinN; u32TmpN < (u32RngMaxN + 1);
> <= and remove + 1
>
> Name variable as n ?
OK. we will use max() and min() and rename u32TmpN as n.
>> + u32TmpN++) {
> One line.
>
>> + u64Con2 = u64Con1 * u32TmpN;
>> + u32RngMinP = 1UL;
>> + u32RngMaxP = 7UL;
> Limits to defines?
OK, will add #define constant for them.
>> + u32RngMinP = ((u64Con2 / VSIPLL_FCLKO_MAX_FREQ) > 1) ?
>> + (u64Con2 / VSIPLL_FCLKO_MAX_FREQ) : 1;
>> + u32RngMaxP = ((u64Con2 / VSIPLL_FCLKO_MIN_FREQ) <
>> + u32RngMaxP) ?
>> + (u64Con2 / VSIPLL_FCLKO_MIN_FREQ) :
>> + u32RngMaxP;
> min & max.
We will fix it.
>
>> + for (u32TmpP = u32RngMinP; u32TmpP < (u32RngMaxP + 1);
> <= and remove +1?
I fix it as "<=".
>
> Name variable as p?
sure
>
>> + u32TmpP++) {
> One line.
>
>> + u64Con3 = u64Con2 / u32TmpP;
>> + if (u64Con3 > u64PllFreq)
>> + u32Tmp = u64Con3 - u64PllFreq;
>> + else
>> + u32Tmp = u64PllFreq - u64Con3;
> abs()?
Yes, we will use abs() instead.
>
>> +
>> + if (u32Tmp < u32Min) {
>> + u32Min = u32Tmp;
>> + u32MinM = u32TmpM;
>> + u32MinN = u32TmpN;
>> + u32MinP = u32TmpP;
>> +
>> + if (u32Min == 0UL) {
> goto out?
>
>> + u32Reg[0] = (u32MinM << 12) |
>> + (u32MinN);
>> + u32Reg[1] = (u32MinP << 4);
>> + return ((PllSrcClk * u32MinN) /
>> + (u32MinP * u32MinM));
>> + }
>> + }
>> + }
>> + }
>> + }
>> +
> out: ?
OK, we will use "goto out".
>> + u32Reg[0] = (u32MinM << 12) | (u32MinN);
> FIELD_PREP() | FIELD_PREP() ?
>
>> + u32Reg[1] = (u32MinP << 4);
> FIELD_PREP() ?
Yes, we will use FIELD_PREP().
>> + u64PllClk = (PllSrcClk * u32MinN) / (u32MinP * u32MinM);
> Use the 64-bit divide from math64.h rather than leave it up to compiler.
OK, we will fix it.
>> + return u64PllClk;
>> +}
>> +
>> +/* VSI-PLL: FRACTIONAL_MODE */
>> +unsigned long CLK_CalPLLFreq_Mode1(unsigned long PllSrcClk,
>> + unsigned long u64PllFreq, u32 *u32Reg)
>> +{
>> + unsigned long u64X, u64N, u64M, u64P, u64tmp;
>> + unsigned long u64PllClk, u64FCLKO;
>> + u32 u32FRAC;
>> +
>> + if (u64PllFreq > VSIPLL_FCLKO_MAX_FREQ) {
>> + u32Reg[0] = ma35d1pll_freq[1].ctl0_reg;
>> + u32Reg[1] = ma35d1pll_freq[1].ctl1_reg;
>> + u64PllClk = ma35d1pll_freq[1].freq;
>> + return u64PllClk;
>> + }
>> +
>> + if (u64PllFreq > (VSIPLL_FCLKO_MIN_FREQ/(100-1))) {
>> + u64FCLKO = u64PllFreq * ((VSIPLL_FCLKO_MIN_FREQ / u64PllFreq) +
>> + ((VSIPLL_FCLKO_MIN_FREQ % u64PllFreq) ? 1 : 0));
> You need to rework this to do 64-bit divide and remainder with
> something that comes from math64.h.
We will fix it. Thanks for the suggestion.
>
>> + } else {
>> + pr_err("Failed to set rate %ld\n", u64PllFreq);
>> + return 0;
>> + }
>> +
>> + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
>> + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
>> + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
> Ditto.
>
> Is here some ...ROUND_UP() trick hidden too?
This follows the description of PLL spec.
>
>> +
>> + if ((PllSrcClk > (VSIPLL_FREFDIVM_MAX_FREQ * (64-1))) ||
>> + (PllSrcClk < VSIPLL_FREFDIVM_MIN_FREQ1))
>> + return 0;
>> +
>> + u64M = (PllSrcClk <= VSIPLL_FREFDIVM_MAX_FREQ) ? 1 :
>> + ((PllSrcClk / VSIPLL_FREFDIVM_MAX_FREQ) +
>> + ((PllSrcClk % VSIPLL_FREFDIVM_MAX_FREQ) ? 1 : 0));
> Ditto.
>
>> +
>> + u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
>> + u64N = u64tmp / 1000;
>> + u64X = u64tmp % 1000;
> math64.h x 3 (or x2 since you can get remainder for free I think).
Got it. Thank you.
>> + u32FRAC = ((u64X << 24) + 500) / 1000;
>> + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
>> +
>> + u32Reg[0] = (u64M << 12) | (u64N);
> FIELD_PREP() ?
>
>> + u32Reg[1] = (u64P << 4) | (u32FRAC << 8);
> FIELD_PREP() ?
Sure, we will use FIELD_PREP().
>> + return u64PllClk;
>> +}
>> +
>> +/* VSI-PLL: SS_MODE */
>> +unsigned long CLK_CalPLLFreq_Mode2(unsigned long PllSrcClk,
>> + unsigned long u64PllFreq,
>> + u32 u32SR, u32 u32Fmod, u32 *u32Reg)
>> +{
>> + unsigned long u64X, u64N, u64M, u64P, u64tmp, u64tmpP, u64tmpM;
>> + unsigned long u64SSRATE, u64SLOPE, u64PllClk, u64FCLKO;
>> + u32 u32FRAC, i;
>> +
>> + if (u64PllFreq >= VSIPLL_FCLKO_MAX_FREQ) {
>> + u32Reg[0] = ma35d1pll_freq[2].ctl0_reg;
>> + u32Reg[1] = ma35d1pll_freq[2].ctl1_reg;
>> + u32Reg[2] = ma35d1pll_freq[2].ctl2_reg;
>> + u64PllClk = ma35d1pll_freq[2].freq;
>> + return u64PllClk;
>> + }
>> +
>> + if (u64PllFreq < VSIPLL_FCLKO_MIN_FREQ) {
>> + u64FCLKO = 0;
>> + for (i = 2; i < 8; i++) {
>> + u64tmp = (i * u64PllFreq);
>> + if (u64tmp > VSIPLL_FCLKO_MIN_FREQ)
> VSIPLL_FCLKO_MAX_FREQ check is not required ?
We will add check VSIPLL_FCLKO_MAX_FREQ.
>
>> + u64FCLKO = u64tmp;
>> + }
>> + if (u64FCLKO == 0) {
>> + pr_err("Failed to set rate %ld\n", u64PllFreq);
>> + return 0;
>> + }
>> +
>> + } else
>> + u64FCLKO = u64PllFreq;
>> +
>> + u64P = 0;
>> + for (i = 1; i < 8; i++) {
>> + u64tmpP = i * u64FCLKO;
>> + if ((u64tmpP <= VSIPLL_FCLK_MAX_FREQ) &&
>> + (u64tmpP >= VSIPLL_FCLK_MIN_FREQ)) {
>> + u64P = i;
>> + break;
>> + }
>> + }
>> +
>> + if (u64P == 0)
>> + return 0;
>> +
>> + u64M = 0;
>> + for (i = 1; i < 64; i++) {
>> + u64tmpM = PllSrcClk / i;
>> + if ((u64tmpM <= VSIPLL_FREFDIVM_MAX_FREQ) &&
>> + (u64tmpM >= VSIPLL_FREFDIVM_MIN_FREQ1)) {
>> + u64M = i;
>> + break;
>> + }
>> + }
>> +
>> + if (u64M == 0)
>> + return 0;
>> +
>> + u64tmp = (u64FCLKO * u64P * u64M * 1000) / PllSrcClk;
>> + u64N = u64tmp / 1000;
>> + u64X = u64tmp % 1000;
>> + u32FRAC = ((u64X << 24) + 500) / 1000;
>> +
>> + u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
>> + u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
>> +
>> + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
> Is some *SEC_PER_*SEC define relevant for 1000 ?
>
> Or some other units, e.g., HZ related?
1000 is for kHz to MHz, and 100 is for percentage.
>
>> +
>> + u32Reg[0] = (u64SSRATE << VSIPLLCTL0_SSRATE_POS) | (u64M <<
>> + VSIPLLCTL0_INDIV_POS) | (u64N);
> FIELD_PREP()
We will fix it.
>
>> + u32Reg[1] = (u64P << VSIPLLCTL1_OUTDIV_POS) | (u32FRAC << VSIPLLCTL1_FRAC_POS);
> Instead of _POS named variables, add GENMASK one instead and use
> FIELD_PREP. You might need to use GENMASK_ULL() for the masks if you are
> dealing with true 64-bitness here instead of the quasi unsigned longs.
Got it. We will modify it.
>> + u32Reg[2] = u64SLOPE;
>> + return u64PllClk;
>> +}
>> +
>> +unsigned long CLK_SetPLLFreq(struct ma35d1_clk_pll *pll,
>> + unsigned long PllSrcClk,
>> + unsigned long u64PllFreq)
>> +{
>> + u32 u32Reg[3] = { 0 }, val_ctl0, val_ctl1, val_ctl2;
>> + unsigned long u64PllClk;
>> +
>> + val_ctl0 = __raw_readl(pll->ctl0_base);
>> + val_ctl1 = __raw_readl(pll->ctl1_base);
>> + val_ctl2 = __raw_readl(pll->ctl2_base);
>> +
>> + switch (pll->mode) {
>> + case VSIPLL_INTEGER_MODE:
>> + u64PllClk = CLK_CalPLLFreq_Mode0(PllSrcClk, u64PllFreq,
>> + u32Reg);
> One line.
It will exceed 80 characters in one line.
>
>> + val_ctl0 = u32Reg[0] |
>> + (VSIPLL_INTEGER_MODE << VSIPLLCTL0_MODE_POS);
> GENMASK() + FIELD_PREP()
I will fix it.
>
>> + break;
>> + case VSIPLL_FRACTIONAL_MODE:
>> + u64PllClk = CLK_CalPLLFreq_Mode1(PllSrcClk, u64PllFreq,
>> + u32Reg);
>> + val_ctl0 = u32Reg[0] |
>> + (VSIPLL_FRACTIONAL_MODE << VSIPLLCTL0_MODE_POS);
> Ditto.
>
>> + break;
>> + case VSIPLL_SS_MODE:
>> + u64PllClk = CLK_CalPLLFreq_Mode2(PllSrcClk, u64PllFreq,
>> + VSIPLL_MODULATION_FREQ,
>> + VSIPLL_SPREAD_RANGE, u32Reg);
>> + val_ctl0 = u32Reg[0] |
>> + (VSIPLL_SS_MODE << VSIPLLCTL0_MODE_POS);
> Ditto.
>
>> + break;
>> + }
>> +
>> + val_ctl1 = VSIPLLCTL1_PD_MSK | u32Reg[1];
>> + val_ctl2 = u32Reg[2];
>> +
>> + __raw_writel(val_ctl0, pll->ctl0_base);
>> + __raw_writel(val_ctl1, pll->ctl1_base);
>> + __raw_writel(val_ctl2, pll->ctl2_base);
>> + return u64PllClk;
>> +}
>> +
>> +unsigned long CLK_GetPLLFreq_VSIPLL(struct ma35d1_clk_pll *pll,
>> + unsigned long PllSrcClk)
>> +{
>> + u32 u32M, u32N, u32P, u32X, u32SR, u32FMOD;
>> + u32 val_ctl0, val_ctl1, val_ctl2;
>> + unsigned long u64PllClk, u64X;
>> +
>> + val_ctl0 = __raw_readl(pll->ctl0_base);
>> + val_ctl1 = __raw_readl(pll->ctl1_base);
>> + val_ctl2 = __raw_readl(pll->ctl2_base);
>> +
>> + if (val_ctl1 & PLLXCTL1_BP_MSK) {
>> + u64PllClk = PllSrcClk;
>> + return u64PllClk;
>> + }
>> +
>> + if (pll->mode == VSIPLL_INTEGER_MODE) {
>> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
>> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
>> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
>> +
>> + u64PllClk = PllSrcClk * u32N;
>> + do_div(u64PllClk, u32M * u32P);
>> +
>> + } else if (pll->mode == VSIPLL_FRACTIONAL_MODE) {
>> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
>> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
>> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
>> + u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
>> + u64X = (u64) u32X;
>> + u64X = (((u64X * 1000) + 500) >> 24);
>> + u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
>> + 1000 / u32P / u32M;
> math64.h
>
> Please fix the remaining ones w/o me noting them down.
Got it. Thank you.
>> +
>> + } else {
>> + u32N = FIELD_GET(PLLXCTL0_FBDIV_MSK, val_ctl0);
>> + u32M = FIELD_GET(PLLXCTL0_INDIV_MSK, val_ctl0);
>> + u32SR = FIELD_GET(PLLXCTL0_SSRATE_MSK, val_ctl0);
>> + u32P = FIELD_GET(PLLXCTL1_OUTDIV_MSK, val_ctl1);
>> + u32X = FIELD_GET(PLLXCTL1_FRAC_MSK, val_ctl1);
>> + u32FMOD = FIELD_GET(PLLXCTL2_SLOPE_MSK, val_ctl2);
>> + u64X = (u64) u32X;
>> + u64X = ((u64X * 1000) >> 24);
>> + u64PllClk = (PllSrcClk * ((u32N * 1000) + u64X)) /
>> + 1000 / u32P / u32M;
>> + }
>> + return u64PllClk;
>> +}
>> +
>> +static int ma35d1_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long parent_rate)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> +
>> + if ((parent_rate < VSIPLL_FREF_MIN_FREQ) ||
>> + (parent_rate > VSIPLL_FREF_MAX_FREQ))
>> + return 0;
>> +
>> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
>> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
>> + return -EACCES;
>> + }
>> + CLK_UnLockReg(pll);
>> + pll->rate = CLK_SetPLLFreq(pll, parent_rate, rate);
>> + CLK_LockReg(pll);
>> + return 0;
>> +}
>> +
>> +static unsigned long ma35d1_clk_pll_recalc_rate(struct clk_hw *hw,
>> + unsigned long parent_rate)
>> +{
>> + unsigned long pllfreq;
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> +
>> + if ((parent_rate < VSIPLL_FREF_MIN_FREQ)
>> + || (parent_rate > VSIPLL_FREF_MAX_FREQ))
>> + return 0;
>> +
>> + switch (pll->type) {
>> + case MA35D1_CAPLL:
>> + pllfreq = CLK_GetPLLFreq_SMICPLL(pll, parent_rate);
>> + break;
>> + case MA35D1_DDRPLL:
>> + case MA35D1_APLL:
>> + case MA35D1_EPLL:
>> + case MA35D1_VPLL:
>> + pllfreq = CLK_GetPLLFreq_VSIPLL(pll, parent_rate);
>> + break;
>> + }
>> +
>> + return pllfreq;
>> +}
>> +
>> +static long ma35d1_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
>> + unsigned long *prate)
>> +{
>> + return rate;
>> +}
>> +
>> +static int ma35d1_clk_pll_is_prepared(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val = __raw_readl(pll->ctl1_base);
>> +
>> + return (val & VSIPLLCTL1_PD_MSK) ? 0 : 1;
> Unnecessary parenthesis
I will remove the parenthesis.
>
>> +}
>> +
>> +static int ma35d1_clk_pll_prepare(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val;
>> +
>> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
>> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
>> + return -EACCES;
>> + }
> Add helper for this, there is more than 1 copy of this.
>
>> +
>> + CLK_UnLockReg(pll);
>> + val = __raw_readl(pll->ctl1_base);
>> + val &= ~VSIPLLCTL1_PD_MSK;
>> + __raw_writel(val, pll->ctl1_base);
>> + CLK_LockReg(pll);
>> + return 0;
>> +}
>> +
>> +static void ma35d1_clk_pll_unprepare(struct clk_hw *hw)
>> +{
>> + struct ma35d1_clk_pll *pll = to_ma35d1_clk_pll(hw);
>> + u32 val;
>> +
>> + if ((pll->type == MA35D1_CAPLL) || (pll->type == MA35D1_DDRPLL)) {
>> + pr_warn("Nuvoton MA35D1 CAPLL/DDRPLL is read only.\n");
>> + } else {
>> + val = __raw_readl(pll->ctl1_base);
>> + val |= VSIPLLCTL1_PD_MSK;
>> + __raw_writel(val, pll->ctl1_base);
>> + }
>> +}
>> +
>> +static const struct clk_ops ma35d1_clk_pll_ops = {
>> + .is_prepared = ma35d1_clk_pll_is_prepared,
>> + .prepare = ma35d1_clk_pll_prepare,
>> + .unprepare = ma35d1_clk_pll_unprepare,
>> + .set_rate = ma35d1_clk_pll_set_rate,
>> + .recalc_rate = ma35d1_clk_pll_recalc_rate,
>> + .round_rate = ma35d1_clk_pll_round_rate,
>> +};
>> +
>> +struct clk_hw *ma35d1_reg_clk_pll(enum ma35d1_pll_type type,
>> + u8 u8mode, const char *name,
>> + const char *parent,
>> + unsigned long targetFreq,
>> + void __iomem *base,
>> + struct regmap *regmap)
>> +{
>> + struct ma35d1_clk_pll *pll;
>> + struct clk_hw *hw;
>> + struct clk_init_data init;
>> + int ret;
>> +
>> + pll = kmalloc(sizeof(*pll), GFP_KERNEL);
>> + if (!pll)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + pll->type = type;
>> + pll->mode = u8mode;
>> + pll->rate = targetFreq;
>> + pll->ctl0_base = base + VSIPLL_CTL0;
>> + pll->ctl1_base = base + VSIPLL_CTL1;
>> + pll->ctl2_base = base + VSIPLL_CTL2;
>> + pll->regmap = regmap;
>> +
>> + init.name = name;
>> + init.flags = 0;
>> + init.parent_names = &parent;
>> + init.num_parents = 1;
>> + init.ops = &ma35d1_clk_pll_ops;
>> + pll->hw.init = &init;
>> + hw = &pll->hw;
>> +
>> + ret = clk_hw_register(NULL, hw);
>> + if (ret) {
>> + pr_err("failed to register vsi-pll clock!!!\n");
> No need to use ! let alone 3 of them.
>
>> + kfree(pll);
>> + return ERR_PTR(ret);
>> + }
>> + return hw;
>> +}
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1.c b/drivers/clk/nuvoton/clk-ma35d1.c
>> new file mode 100644
>> index 000000000000..ac8154458b81
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1.c
>> @@ -0,0 +1,970 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/clkdev.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_address.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/spinlock.h>
>> +#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
>> +
>> +#include "clk-ma35d1.h"
>> +
>> +DEFINE_SPINLOCK(ma35d1_lock);
>> +
>> +static const char *const ca35clk_sel_clks[] = {
>> + "hxt", "capll", "ddrpll", "dummy"
>> +};
>> +
>> +static const char *const sysclk0_sel_clks[] = {
>> + "epll_div2", "syspll"
>> +};
>> +
>> +static const char *const sysclk1_sel_clks[] = {
>> + "hxt", "syspll"
>> +};
>> +
>> +static const char *const axiclk_sel_clks[] = {
>> + "capll_div2", "capll_div4"
>> +};
>> +
>> +static const char *const ccap_sel_clks[] = {
>> + "hxt", "vpll", "apll", "syspll"
>> +};
>> +
>> +static const char *const sdh_sel_clks[] = {
>> + "syspll", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const dcu_sel_clks[] = {
>> + "epll_div2", "syspll"
>> +};
>> +
>> +static const char *const gfx_sel_clks[] = {
>> + "epll", "syspll"
>> +};
>> +
>> +static const char *const dbg_sel_clks[] = {
>> + "hirc", "syspll"
>> +};
>> +
>> +static const char *const timer0_sel_clks[] = {
>> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer1_sel_clks[] = {
>> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer2_sel_clks[] = {
>> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer3_sel_clks[] = {
>> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer4_sel_clks[] = {
>> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer5_sel_clks[] = {
>> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer6_sel_clks[] = {
>> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer7_sel_clks[] = {
>> + "hxt", "lxt", "pclk0", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer8_sel_clks[] = {
>> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer9_sel_clks[] = {
>> + "hxt", "lxt", "pclk1", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer10_sel_clks[] = {
>> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const timer11_sel_clks[] = {
>> + "hxt", "lxt", "pclk2", "dummy", "dummy", "lirc", "dummy", "hirc"
>> +};
>> +
>> +static const char *const uart_sel_clks[] = {
>> + "hxt", "sysclk1_div2", "dummy", "dummy"
>> +};
>> +
>> +static const char *const wdt0_sel_clks[] = {
>> + "dummy", "lxt", "pclk3_div4096", "lirc"
>> +};
>> +
>> +static const char *const wdt1_sel_clks[] = {
>> + "dummy", "lxt", "pclk3_div4096", "lirc"
>> +};
>> +
>> +static const char *const wdt2_sel_clks[] = {
>> + "dummy", "lxt", "pclk4_div4096", "lirc"
>> +};
>> +
>> +static const char *const wwdt0_sel_clks[] = {
>> + "dummy", "dummy", "pclk3_div4096", "lirc"
>> +};
>> +
>> +static const char *const wwdt1_sel_clks[] = {
>> + "dummy", "dummy", "pclk3_div4096", "lirc"
>> +};
>> +
>> +static const char *const wwdt2_sel_clks[] = {
>> + "dummy", "dummy", "pclk4_div4096", "lirc"
>> +};
>> +
>> +static const char *const spi0_sel_clks[] = {
>> + "pclk1", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const spi1_sel_clks[] = {
>> + "pclk2", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const spi2_sel_clks[] = {
>> + "pclk1", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const spi3_sel_clks[] = {
>> + "pclk2", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const qspi0_sel_clks[] = {
>> + "pclk0", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const qspi1_sel_clks[] = {
>> + "pclk0", "apll", "dummy", "dummy"
>> +};
>> +
>> +static const char *const i2s0_sel_clks[] = {
>> + "apll", "sysclk1_div2", "dummy", "dummy"
>> +};
>> +
>> +static const char *const i2s1_sel_clks[] = {
>> + "apll", "sysclk1_div2", "dummy", "dummy"
>> +};
>> +
>> +static const char *const can_sel_clks[] = {
>> + "apll", "vpll"
>> +};
>> +
>> +static const char *const cko_sel_clks[] = {
>> + "hxt", "lxt", "hirc", "lirc", "capll_div4", "syspll",
>> + "ddrpll", "epll_div2", "apll", "vpll", "dummy", "dummy",
>> + "dummy", "dummy", "dummy", "dummy"
>> +};
>> +
>> +static const char *const smc_sel_clks[] = {
>> + "hxt", "pclk4"
>> +};
>> +
>> +static const char *const kpi_sel_clks[] = {
>> + "hxt", "lxt"
>> +};
>> +
>> +static const struct clk_div_table ip_div_table[] = {
>> + {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
>> + {5, 12}, {6, 14}, {7, 16}, {0, 0},
>> +};
>> +
>> +static const struct clk_div_table eadc_div_table[] = {
>> + {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10},
>> + {5, 12}, {6, 14}, {7, 16}, {8, 18},
>> + {9, 20}, {10, 22}, {11, 24}, {12, 26},
>> + {13, 28}, {14, 30}, {15, 32}, {0, 0},
>> +};
>> +
>> +static struct clk_hw **hws;
>> +static struct clk_hw_onecell_data *ma35d1_hw_data;
>> +
>> +static int ma35d1_clocks_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct device *dev = &pdev->dev;
>> + struct device_node *clk_node = dev->of_node;
>> + void __iomem *clk_base;
>> + struct regmap *regmap;
>> + u32 pllmode[5] = { 0, 0, 0, 0, 0 };
>> + u32 pllfreq[5] = { 0, 0, 0, 0, 0 };
>> +
>> + dev_info(&pdev->dev, "Nuvoton MA35D1 Clock Driver\n");
>> + ma35d1_hw_data = devm_kzalloc(&pdev->dev, struct_size(ma35d1_hw_data,
>> + hws, CLK_MAX_IDX), GFP_KERNEL);
>> +
>> + if (WARN_ON(!ma35d1_hw_data))
>> + return -ENOMEM;
>> +
>> + ma35d1_hw_data->num = CLK_MAX_IDX;
>> + hws = ma35d1_hw_data->hws;
>> +
>> + clk_node = of_find_compatible_node(NULL, NULL, "nuvoton,ma35d1-clk");
>> + clk_base = of_iomap(clk_node, 0);
>> + of_node_put(clk_node);
>> + if (!clk_base) {
>> + pr_err("%s: could not map region\n", __func__);
>> + return -ENOMEM;
>> + }
>> + regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
>> + "nuvoton,sys");
>> + if (IS_ERR(regmap))
>> + pr_warn("%s: Unable to get syscon\n", __func__);
> Don't print __func__ to user.
I will remove the __func__.
>
>> +
>> + /* clock sources */
>> + hws[HXT] = ma35d1_clk_fixed("hxt", 24000000);
>> + hws[HXT_GATE] = ma35d1_clk_gate("hxt_gate", "hxt",
>> + clk_base + REG_CLK_PWRCTL, 0);
>> + hws[LXT] = ma35d1_clk_fixed("lxt", 32768);
>> + hws[LXT_GATE] = ma35d1_clk_gate("lxt_gate", "lxt",
>> + clk_base + REG_CLK_PWRCTL, 1);
>> + hws[HIRC] = ma35d1_clk_fixed("hirc", 12000000);
>> + hws[HIRC_GATE] = ma35d1_clk_gate("hirc_gate", "hirc",
>> + clk_base + REG_CLK_PWRCTL, 2);
>> + hws[LIRC] = ma35d1_clk_fixed("lirc", 32000);
>> + hws[LIRC_GATE] = ma35d1_clk_gate("lirc_gate", "lirc",
>> + clk_base + REG_CLK_PWRCTL, 3);
>> +
>> + /* PLL */
>> + of_property_read_u32_array(clk_node, "clock-pll-mode", pllmode,
>> + ARRAY_SIZE(pllmode));
>> + of_property_read_u32_array(clk_node, "assigned-clock-rates", pllfreq,
>> + ARRAY_SIZE(pllfreq));
>> +
>> + /* SMIC PLL */
>> + hws[CAPLL] = ma35d1_reg_clk_pll(MA35D1_CAPLL, pllmode[0], "capll",
>> + "hxt", pllfreq[0],
>> + clk_base + REG_CLK_PLL0CTL0, regmap);
>> + hws[SYSPLL] = ma35d1_clk_fixed("syspll", 180000000);
>> +
>> + /* VSI PLL */
>> + hws[DDRPLL] = ma35d1_reg_clk_pll(MA35D1_DDRPLL, pllmode[1], "ddrpll",
>> + "hxt", pllfreq[1],
>> + clk_base + REG_CLK_PLL2CTL0, regmap);
>> + hws[APLL] = ma35d1_reg_clk_pll(MA35D1_APLL, pllmode[2], "apll", "hxt",
>> + pllfreq[2], clk_base + REG_CLK_PLL3CTL0,
>> + regmap);
>> + hws[EPLL] = ma35d1_reg_clk_pll(MA35D1_EPLL, pllmode[3], "epll", "hxt",
>> + pllfreq[3], clk_base + REG_CLK_PLL4CTL0,
>> + regmap);
>> + hws[VPLL] = ma35d1_reg_clk_pll(MA35D1_VPLL, pllmode[4], "vpll", "hxt",
>> + pllfreq[4], clk_base + REG_CLK_PLL5CTL0,
>> + regmap);
>> + hws[EPLL_DIV2] = ma35d1_clk_fixed_factor("epll_div2", "epll", 1, 2);
>> + hws[EPLL_DIV4] = ma35d1_clk_fixed_factor("epll_div4", "epll", 1, 4);
>> + hws[EPLL_DIV8] = ma35d1_clk_fixed_factor("epll_div8", "epll", 1, 8);
>> +
>> + /* CA35 */
>> + hws[CA35CLK_MUX] = ma35d1_clk_mux("ca35clk_mux",
>> + clk_base + REG_CLK_CLKSEL0, 0,
>> + 2, ca35clk_sel_clks,
>> + ARRAY_SIZE(ca35clk_sel_clks));
>> +
>> + /* AXI */
>> + hws[AXICLK_DIV2] = ma35d1_clk_fixed_factor("capll_div2", "ca35clk_mux",
>> + 1, 2);
>> + hws[AXICLK_DIV4] = ma35d1_clk_fixed_factor("capll_div4", "ca35clk_mux",
>> + 1, 4);
>> + hws[AXICLK_MUX] = ma35d1_clk_mux("axiclk_mux",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 26, 1, axiclk_sel_clks,
>> + ARRAY_SIZE(axiclk_sel_clks));
>> +
>> + /* SYSCLK0 & SYSCLK1 */
>> + hws[SYSCLK0_MUX] = ma35d1_clk_mux("sysclk0_mux",
>> + clk_base + REG_CLK_CLKSEL0,
>> + 2, 1, sysclk0_sel_clks,
>> + ARRAY_SIZE(sysclk0_sel_clks));
>> + hws[SYSCLK1_MUX] = ma35d1_clk_mux("sysclk1_mux",
>> + clk_base + REG_CLK_CLKSEL0,
>> + 4, 1, sysclk1_sel_clks,
>> + ARRAY_SIZE(sysclk1_sel_clks));
>> + hws[SYSCLK1_DIV2] = ma35d1_clk_fixed_factor("sysclk1_div2",
>> + "sysclk1_mux", 1, 2);
>> +
>> + /* HCLK0~3 & PCLK0~4 */
>> + hws[HCLK0] = ma35d1_clk_fixed_factor("hclk0", "sysclk1_mux", 1, 1);
>> + hws[HCLK1] = ma35d1_clk_fixed_factor("hclk1", "sysclk1_mux", 1, 1);
>> + hws[HCLK2] = ma35d1_clk_fixed_factor("hclk2", "sysclk1_mux", 1, 1);
>> + hws[PCLK0] = ma35d1_clk_fixed_factor("pclk0", "sysclk1_mux", 1, 1);
>> + hws[PCLK1] = ma35d1_clk_fixed_factor("pclk1", "sysclk1_mux", 1, 1);
>> + hws[PCLK2] = ma35d1_clk_fixed_factor("pclk2", "sysclk1_mux", 1, 1);
>> +
>> + hws[HCLK3] = ma35d1_clk_fixed_factor("hclk3", "sysclk1_mux", 1, 2);
>> + hws[PCLK3] = ma35d1_clk_fixed_factor("pclk3", "sysclk1_mux", 1, 2);
>> + hws[PCLK4] = ma35d1_clk_fixed_factor("pclk4", "sysclk1_mux", 1, 2);
>> +
>> + hws[USBPHY0] = ma35d1_clk_fixed("usbphy0", 480000000);
>> + hws[USBPHY1] = ma35d1_clk_fixed("usbphy1", 480000000);
>> +
>> + /* DDR */
>> + hws[DDR0_GATE] = ma35d1_clk_gate("ddr0_gate", "ddrpll",
>> + clk_base + REG_CLK_SYSCLK0, 4);
>> + hws[DDR6_GATE] = ma35d1_clk_gate("ddr6_gate", "ddrpll",
>> + clk_base + REG_CLK_SYSCLK0, 5);
>> +
>> + /* CAN0 */
>> + hws[CAN0_MUX] = ma35d1_clk_mux("can0_mux", clk_base + REG_CLK_CLKSEL4,
>> + 16, 1, can_sel_clks,
>> + ARRAY_SIZE(can_sel_clks));
>> + hws[CAN0_DIV] = ma35d1_clk_divider_table("can0_div", "can0_mux",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 0, 3, ip_div_table);
>> + hws[CAN0_GATE] = ma35d1_clk_gate("can0_gate", "can0_div",
>> + clk_base + REG_CLK_SYSCLK0, 8);
>> +
>> + /* CAN1 */
>> + hws[CAN1_MUX] = ma35d1_clk_mux("can1_mux", clk_base + REG_CLK_CLKSEL4,
>> + 17, 1, can_sel_clks,
>> + ARRAY_SIZE(can_sel_clks));
>> + hws[CAN1_DIV] = ma35d1_clk_divider_table("can1_div", "can1_mux",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 4, 3, ip_div_table);
>> + hws[CAN1_GATE] = ma35d1_clk_gate("can1_gate", "can1_div",
>> + clk_base + REG_CLK_SYSCLK0, 9);
>> +
>> + /* CAN2 */
>> + hws[CAN2_MUX] = ma35d1_clk_mux("can2_mux", clk_base + REG_CLK_CLKSEL4,
>> + 18, 1, can_sel_clks,
>> + ARRAY_SIZE(can_sel_clks));
>> + hws[CAN2_DIV] = ma35d1_clk_divider_table("can2_div", "can2_mux",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 8, 3, ip_div_table);
>> + hws[CAN2_GATE] = ma35d1_clk_gate("can2_gate", "can2_div",
>> + clk_base + REG_CLK_SYSCLK0, 10);
>> +
>> + /* CAN3 */
>> + hws[CAN3_MUX] = ma35d1_clk_mux("can3_mux", clk_base + REG_CLK_CLKSEL4,
>> + 19, 1, can_sel_clks,
>> + ARRAY_SIZE(can_sel_clks));
>> + hws[CAN3_DIV] = ma35d1_clk_divider_table("can3_div", "can3_mux",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 12, 3, ip_div_table);
>> + hws[CAN3_GATE] = ma35d1_clk_gate("can3_gate", "can3_div",
>> + clk_base + REG_CLK_SYSCLK0, 11);
>> +
>> + /* SDH0 */
>> + hws[SDH0_MUX] = ma35d1_clk_mux("sdh0_mux", clk_base + REG_CLK_CLKSEL0,
>> + 16, 2, sdh_sel_clks,
>> + ARRAY_SIZE(sdh_sel_clks));
>> + hws[SDH0_GATE] = ma35d1_clk_gate("sdh0_gate", "sdh0_mux",
>> + clk_base + REG_CLK_SYSCLK0, 16);
>> +
>> + /* SDH1 */
>> + hws[SDH1_MUX] = ma35d1_clk_mux("sdh1_mux", clk_base + REG_CLK_CLKSEL0,
>> + 18, 2, sdh_sel_clks,
>> + ARRAY_SIZE(sdh_sel_clks));
>> + hws[SDH1_GATE] = ma35d1_clk_gate("sdh1_gate", "sdh1_mux",
>> + clk_base + REG_CLK_SYSCLK0, 17);
>> +
>> + /* NAND */
>> + hws[NAND_GATE] = ma35d1_clk_gate("nand_gate", "hclk1",
>> + clk_base + REG_CLK_SYSCLK0, 18);
>> +
>> + /* USB */
>> + hws[USBD_GATE] = ma35d1_clk_gate("usbd_gate", "usbphy0",
>> + clk_base + REG_CLK_SYSCLK0, 19);
>> + hws[USBH_GATE] = ma35d1_clk_gate("usbh_gate", "usbphy0",
>> + clk_base + REG_CLK_SYSCLK0, 20);
>> + hws[HUSBH0_GATE] = ma35d1_clk_gate("husbh0_gate", "usbphy0",
>> + clk_base + REG_CLK_SYSCLK0, 21);
>> + hws[HUSBH1_GATE] = ma35d1_clk_gate("husbh1_gate", "usbphy0",
>> + clk_base + REG_CLK_SYSCLK0, 22);
>> +
>> + /* GFX */
>> + hws[GFX_MUX] = ma35d1_clk_mux("gfx_mux", clk_base + REG_CLK_CLKSEL0,
>> + 26, 1, gfx_sel_clks,
>> + ARRAY_SIZE(gfx_sel_clks));
>> + hws[GFX_GATE] = ma35d1_clk_gate("gfx_gate", "gfx_mux",
>> + clk_base + REG_CLK_SYSCLK0, 24);
>> +
>> + /* VC8K */
>> + hws[VC8K_GATE] = ma35d1_clk_gate("vc8k_gate", "sysclk0_mux",
>> + clk_base + REG_CLK_SYSCLK0, 25);
>> +
>> + /* DCU */
>> + hws[DCU_MUX] = ma35d1_clk_mux("dcu_mux", clk_base + REG_CLK_CLKSEL0,
>> + 24, 1, dcu_sel_clks,
>> + ARRAY_SIZE(dcu_sel_clks));
>> + hws[DCU_GATE] = ma35d1_clk_gate("dcu_gate", "dcu_mux",
>> + clk_base + REG_CLK_SYSCLK0, 26);
>> +
>> + /* DCUP */
>> + hws[DCUP_DIV] = ma35d1_clk_divider_table("dcup_div", "vpll",
>> + clk_base + REG_CLK_CLKDIV0,
>> + 16, 3, ip_div_table);
>> +
>> + /* EMAC0 */
>> + hws[EMAC0_GATE] = ma35d1_clk_gate("emac0_gate", "epll_div2",
>> + clk_base + REG_CLK_SYSCLK0, 27);
>> +
>> + /* EMAC1 */
>> + hws[EMAC1_GATE] = ma35d1_clk_gate("emac1_gate", "epll_div2",
>> + clk_base + REG_CLK_SYSCLK0, 28);
>> +
>> + /* CCAP0 */
>> + hws[CCAP0_MUX] = ma35d1_clk_mux("ccap0_mux",
>> + clk_base + REG_CLK_CLKSEL0,
>> + 12, 1, ccap_sel_clks,
>> + ARRAY_SIZE(ccap_sel_clks));
>> + hws[CCAP0_DIV] = ma35d1_clk_divider("ccap0_div", "ccap0_mux",
>> + clk_base + REG_CLK_CLKDIV1, 8, 4);
>> + hws[CCAP0_GATE] = ma35d1_clk_gate("ccap0_gate", "ccap0_div",
>> + clk_base + REG_CLK_SYSCLK0, 29);
>> +
>> + /* CCAP1 */
>> + hws[CCAP1_MUX] = ma35d1_clk_mux("ccap1_mux",
>> + clk_base + REG_CLK_CLKSEL0,
>> + 14, 1, ccap_sel_clks,
>> + ARRAY_SIZE(ccap_sel_clks));
>> + hws[CCAP1_DIV] = ma35d1_clk_divider("ccap1_div", "ccap1_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 12, 4);
>> + hws[CCAP1_GATE] = ma35d1_clk_gate("ccap1_gate", "ccap1_div",
>> + clk_base + REG_CLK_SYSCLK0, 30);
>> +
>> + /* PDMA0~3 */
>> + hws[PDMA0_GATE] = ma35d1_clk_gate("pdma0_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 0);
>> + hws[PDMA1_GATE] = ma35d1_clk_gate("pdma1_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 1);
>> + hws[PDMA2_GATE] = ma35d1_clk_gate("pdma2_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 2);
>> + hws[PDMA3_GATE] = ma35d1_clk_gate("pdma3_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 3);
>> +
>> + /* WH0~1 */
>> + hws[WH0_GATE] = ma35d1_clk_gate("wh0_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 4);
>> + hws[WH1_GATE] = ma35d1_clk_gate("wh1_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 5);
>> +
>> + /* HWS */
>> + hws[HWS_GATE] = ma35d1_clk_gate("hws_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 6);
>> +
>> + /* EBI */
>> + hws[EBI_GATE] = ma35d1_clk_gate("ebi_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 7);
>> +
>> + /* SRAM0~1 */
>> + hws[SRAM0_GATE] = ma35d1_clk_gate("sram0_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 8);
>> + hws[SRAM1_GATE] = ma35d1_clk_gate("sram1_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 9);
>> +
>> + /* ROM */
>> + hws[ROM_GATE] = ma35d1_clk_gate("rom_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 10);
>> +
>> + /* TRA */
>> + hws[TRA_GATE] = ma35d1_clk_gate("tra_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 11);
>> +
>> + /* DBG */
>> + hws[DBG_MUX] = ma35d1_clk_mux("dbg_mux", clk_base + REG_CLK_CLKSEL0,
>> + 27, 1, dbg_sel_clks,
>> + ARRAY_SIZE(dbg_sel_clks));
>> + hws[DBG_GATE] = ma35d1_clk_gate("dbg_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 12);
>> +
>> + /* CLKO */
>> + hws[CKO_MUX] = ma35d1_clk_mux("cko_mux", clk_base + REG_CLK_CLKSEL4,
>> + 24, 4, cko_sel_clks,
>> + ARRAY_SIZE(cko_sel_clks));
>> + hws[CKO_DIV] = ma35d1_clk_divider_pow2("cko_div", "cko_mux",
>> + clk_base + REG_CLK_CLKOCTL,
>> + 0, 4);
>> + hws[CKO_GATE] = ma35d1_clk_gate("cko_gate", "cko_div",
>> + clk_base + REG_CLK_SYSCLK1, 13);
>> +
>> + /* GTMR */
>> + hws[GTMR_GATE] = ma35d1_clk_gate("gtmr_gate", "hirc",
>> + clk_base + REG_CLK_SYSCLK1, 14);
>> +
>> + /* GPIO */
>> + hws[GPA_GATE] = ma35d1_clk_gate("gpa_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 16);
>> + hws[GPB_GATE] = ma35d1_clk_gate("gpb_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 17);
>> + hws[GPC_GATE] = ma35d1_clk_gate("gpc_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 18);
>> + hws[GPD_GATE] = ma35d1_clk_gate("gpd_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 19);
>> + hws[GPE_GATE] = ma35d1_clk_gate("gpe_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 20);
>> + hws[GPF_GATE] = ma35d1_clk_gate("gpf_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 21);
>> + hws[GPG_GATE] = ma35d1_clk_gate("gpg_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 22);
>> + hws[GPH_GATE] = ma35d1_clk_gate("gph_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 23);
>> + hws[GPI_GATE] = ma35d1_clk_gate("gpi_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 24);
>> + hws[GPJ_GATE] = ma35d1_clk_gate("gpj_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 25);
>> + hws[GPK_GATE] = ma35d1_clk_gate("gpk_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 26);
>> + hws[GPL_GATE] = ma35d1_clk_gate("gpl_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 27);
>> + hws[GPM_GATE] = ma35d1_clk_gate("gpm_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 28);
>> + hws[GPN_GATE] = ma35d1_clk_gate("gpn_gate", "hclk0",
>> + clk_base + REG_CLK_SYSCLK1, 29);
>> +
>> + /* TIMER0~11 */
>> + hws[TMR0_MUX] = ma35d1_clk_mux("tmr0_mux", clk_base + REG_CLK_CLKSEL1,
>> + 0, 3, timer0_sel_clks,
>> + ARRAY_SIZE(timer0_sel_clks));
>> + hws[TMR0_GATE] = ma35d1_clk_gate("tmr0_gate", "tmr0_mux",
>> + clk_base + REG_CLK_APBCLK0, 0);
>> + hws[TMR1_MUX] = ma35d1_clk_mux("tmr1_mux", clk_base + REG_CLK_CLKSEL1,
>> + 4, 3, timer1_sel_clks,
>> + ARRAY_SIZE(timer1_sel_clks));
>> + hws[TMR1_GATE] = ma35d1_clk_gate("tmr1_gate", "tmr1_mux",
>> + clk_base + REG_CLK_APBCLK0, 1);
>> + hws[TMR2_MUX] = ma35d1_clk_mux("tmr2_mux", clk_base + REG_CLK_CLKSEL1,
>> + 8, 3, timer2_sel_clks,
>> + ARRAY_SIZE(timer2_sel_clks));
>> + hws[TMR2_GATE] = ma35d1_clk_gate("tmr2_gate", "tmr2_mux",
>> + clk_base + REG_CLK_APBCLK0, 2);
>> + hws[TMR3_MUX] = ma35d1_clk_mux("tmr3_mux", clk_base + REG_CLK_CLKSEL1,
>> + 12, 3, timer3_sel_clks,
>> + ARRAY_SIZE(timer3_sel_clks));
>> + hws[TMR3_GATE] = ma35d1_clk_gate("tmr3_gate", "tmr3_mux",
>> + clk_base + REG_CLK_APBCLK0, 3);
>> + hws[TMR4_MUX] = ma35d1_clk_mux("tmr4_mux", clk_base + REG_CLK_CLKSEL1,
>> + 16, 3, timer4_sel_clks,
>> + ARRAY_SIZE(timer4_sel_clks));
>> + hws[TMR4_GATE] = ma35d1_clk_gate("tmr4_gate", "tmr4_mux",
>> + clk_base + REG_CLK_APBCLK0, 4);
>> + hws[TMR5_MUX] = ma35d1_clk_mux("tmr5_mux", clk_base + REG_CLK_CLKSEL1,
>> + 20, 3, timer5_sel_clks,
>> + ARRAY_SIZE(timer5_sel_clks));
>> + hws[TMR5_GATE] = ma35d1_clk_gate("tmr5_gate", "tmr5_mux",
>> + clk_base + REG_CLK_APBCLK0, 5);
>> + hws[TMR6_MUX] = ma35d1_clk_mux("tmr6_mux", clk_base + REG_CLK_CLKSEL1,
>> + 24, 3, timer6_sel_clks,
>> + ARRAY_SIZE(timer6_sel_clks));
>> + hws[TMR6_GATE] = ma35d1_clk_gate("tmr6_gate", "tmr6_mux",
>> + clk_base + REG_CLK_APBCLK0, 6);
>> + hws[TMR7_MUX] = ma35d1_clk_mux("tmr7_mux", clk_base + REG_CLK_CLKSEL1,
>> + 28, 3, timer7_sel_clks,
>> + ARRAY_SIZE(timer7_sel_clks));
>> + hws[TMR7_GATE] = ma35d1_clk_gate("tmr7_gate", "tmr7_mux",
>> + clk_base + REG_CLK_APBCLK0, 7);
>> + hws[TMR8_MUX] = ma35d1_clk_mux("tmr8_mux", clk_base + REG_CLK_CLKSEL2,
>> + 0, 3, timer8_sel_clks,
>> + ARRAY_SIZE(timer8_sel_clks));
>> + hws[TMR8_GATE] = ma35d1_clk_gate("tmr8_gate", "tmr8_mux",
>> + clk_base + REG_CLK_APBCLK0, 8);
>> + hws[TMR9_MUX] = ma35d1_clk_mux("tmr9_mux", clk_base + REG_CLK_CLKSEL2,
>> + 4, 3, timer9_sel_clks,
>> + ARRAY_SIZE(timer9_sel_clks));
>> + hws[TMR9_GATE] = ma35d1_clk_gate("tmr9_gate", "tmr9_mux",
>> + clk_base + REG_CLK_APBCLK0, 9);
>> + hws[TMR10_MUX] = ma35d1_clk_mux("tmr10_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 8, 3, timer10_sel_clks,
>> + ARRAY_SIZE(timer10_sel_clks));
>> + hws[TMR10_GATE] = ma35d1_clk_gate("tmr10_gate", "tmr10_mux",
>> + clk_base + REG_CLK_APBCLK0, 10);
>> + hws[TMR11_MUX] = ma35d1_clk_mux("tmr11_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 12, 3, timer11_sel_clks,
>> + ARRAY_SIZE(timer11_sel_clks));
>> + hws[TMR11_GATE] = ma35d1_clk_gate("tmr11_gate", "tmr11_mux",
>> + clk_base + REG_CLK_APBCLK0, 11);
>> +
>> + /* UART0~16 */
>> + hws[UART0_MUX] = ma35d1_clk_mux("uart0_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 16, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART0_DIV] = ma35d1_clk_divider("uart0_div", "uart0_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 16, 4);
>> + hws[UART0_GATE] = ma35d1_clk_gate("uart0_gate", "uart0_div",
>> + clk_base + REG_CLK_APBCLK0, 12);
>> + hws[UART1_MUX] = ma35d1_clk_mux("uart1_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 18, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART1_DIV] = ma35d1_clk_divider("uart1_div", "uart1_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 20, 4);
>> + hws[UART1_GATE] = ma35d1_clk_gate("uart1_gate", "uart1_div",
>> + clk_base + REG_CLK_APBCLK0, 13);
>> + hws[UART2_MUX] = ma35d1_clk_mux("uart2_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 20, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART2_DIV] = ma35d1_clk_divider("uart2_div", "uart2_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 24, 4);
>> + hws[UART2_GATE] = ma35d1_clk_gate("uart2_gate", "uart2_div",
>> + clk_base + REG_CLK_APBCLK0, 14);
>> + hws[UART3_MUX] = ma35d1_clk_mux("uart3_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 22, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART3_DIV] = ma35d1_clk_divider("uart3_div", "uart3_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 28, 4);
>> + hws[UART3_GATE] = ma35d1_clk_gate("uart3_gate", "uart3_div",
>> + clk_base + REG_CLK_APBCLK0, 15);
>> + hws[UART4_MUX] = ma35d1_clk_mux("uart4_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 24, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART4_DIV] = ma35d1_clk_divider("uart4_div", "uart4_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 0, 4);
>> + hws[UART4_GATE] = ma35d1_clk_gate("uart4_gate", "uart4_div",
>> + clk_base + REG_CLK_APBCLK0, 16);
>> + hws[UART5_MUX] = ma35d1_clk_mux("uart5_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 26, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART5_DIV] = ma35d1_clk_divider("uart5_div", "uart5_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 4, 4);
>> + hws[UART5_GATE] = ma35d1_clk_gate("uart5_gate", "uart5_div",
>> + clk_base + REG_CLK_APBCLK0, 17);
>> + hws[UART6_MUX] = ma35d1_clk_mux("uart6_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 28, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART6_DIV] = ma35d1_clk_divider("uart6_div", "uart6_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 8, 4);
>> + hws[UART6_GATE] = ma35d1_clk_gate("uart6_gate", "uart6_div",
>> + clk_base + REG_CLK_APBCLK0, 18);
>> + hws[UART7_MUX] = ma35d1_clk_mux("uart7_mux",
>> + clk_base + REG_CLK_CLKSEL2,
>> + 30, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART7_DIV] = ma35d1_clk_divider("uart7_div", "uart7_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 12, 4);
>> + hws[UART7_GATE] = ma35d1_clk_gate("uart7_gate", "uart7_div",
>> + clk_base + REG_CLK_APBCLK0, 19);
>> + hws[UART8_MUX] = ma35d1_clk_mux("uart8_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 0, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART8_DIV] = ma35d1_clk_divider("uart8_div", "uart8_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 16, 4);
>> + hws[UART8_GATE] = ma35d1_clk_gate("uart8_gate", "uart8_div",
>> + clk_base + REG_CLK_APBCLK0, 20);
>> + hws[UART9_MUX] = ma35d1_clk_mux("uart9_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 2, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART9_DIV] = ma35d1_clk_divider("uart9_div", "uart9_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 20, 4);
>> + hws[UART9_GATE] = ma35d1_clk_gate("uart9_gate", "uart9_div",
>> + clk_base + REG_CLK_APBCLK0, 21);
>> + hws[UART10_MUX] = ma35d1_clk_mux("uart10_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 4, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART10_DIV] = ma35d1_clk_divider("uart10_div", "uart10_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 24, 4);
>> + hws[UART10_GATE] = ma35d1_clk_gate("uart10_gate", "uart10_div",
>> + clk_base + REG_CLK_APBCLK0, 22);
>> + hws[UART11_MUX] = ma35d1_clk_mux("uart11_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 6, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART11_DIV] = ma35d1_clk_divider("uart11_div", "uart11_mux",
>> + clk_base + REG_CLK_CLKDIV2,
>> + 28, 4);
>> + hws[UART11_GATE] = ma35d1_clk_gate("uart11_gate", "uart11_div",
>> + clk_base + REG_CLK_APBCLK0, 23);
>> + hws[UART12_MUX] = ma35d1_clk_mux("uart12_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 8, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART12_DIV] = ma35d1_clk_divider("uart12_div", "uart12_mux",
>> + clk_base + REG_CLK_CLKDIV3,
>> + 0, 4);
>> + hws[UART12_GATE] = ma35d1_clk_gate("uart12_gate", "uart12_div",
>> + clk_base + REG_CLK_APBCLK0, 24);
>> + hws[UART13_MUX] = ma35d1_clk_mux("uart13_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 10, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART13_DIV] = ma35d1_clk_divider("uart13_div", "uart13_mux",
>> + clk_base + REG_CLK_CLKDIV3,
>> + 4, 4);
>> + hws[UART13_GATE] = ma35d1_clk_gate("uart13_gate", "uart13_div",
>> + clk_base + REG_CLK_APBCLK0, 25);
>> + hws[UART14_MUX] = ma35d1_clk_mux("uart14_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 12, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART14_DIV] = ma35d1_clk_divider("uart14_div", "uart14_mux",
>> + clk_base + REG_CLK_CLKDIV3,
>> + 8, 4);
>> + hws[UART14_GATE] = ma35d1_clk_gate("uart14_gate", "uart14_div",
>> + clk_base + REG_CLK_APBCLK0, 26);
>> + hws[UART15_MUX] = ma35d1_clk_mux("uart15_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 14, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART15_DIV] = ma35d1_clk_divider("uart15_div", "uart15_mux",
>> + clk_base + REG_CLK_CLKDIV3,
>> + 12, 4);
>> + hws[UART15_GATE] = ma35d1_clk_gate("uart15_gate", "uart15_div",
>> + clk_base + REG_CLK_APBCLK0, 27);
>> + hws[UART16_MUX] = ma35d1_clk_mux("uart16_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 16, 2, uart_sel_clks,
>> + ARRAY_SIZE(uart_sel_clks));
>> + hws[UART16_DIV] = ma35d1_clk_divider("uart16_div", "uart16_mux",
>> + clk_base + REG_CLK_CLKDIV3,
>> + 16, 4);
>> + hws[UART16_GATE] = ma35d1_clk_gate("uart16_gate", "uart16_div",
>> + clk_base + REG_CLK_APBCLK0, 28);
>> +
>> + /* RTC */
>> + hws[RTC_GATE] = ma35d1_clk_gate("rtc_gate", "lxt",
>> + clk_base + REG_CLK_APBCLK0, 29);
>> +
>> + /* DDRP */
>> + hws[DDR_GATE] = ma35d1_clk_gate("ddr_gate", "ddrpll",
>> + clk_base + REG_CLK_APBCLK0, 30);
>> +
>> + /* KPI */
>> + hws[KPI_MUX] = ma35d1_clk_mux("kpi_mux", clk_base + REG_CLK_CLKSEL4,
>> + 30, 1, kpi_sel_clks,
>> + ARRAY_SIZE(kpi_sel_clks));
>> + hws[KPI_DIV] = ma35d1_clk_divider("kpi_div", "kpi_mux",
>> + clk_base + REG_CLK_CLKDIV4,
>> + 24, 8);
>> + hws[KPI_GATE] = ma35d1_clk_gate("kpi_gate", "kpi_div",
>> + clk_base + REG_CLK_APBCLK0, 31);
>> +
>> + /* I2C0~5 */
>> + hws[I2C0_GATE] = ma35d1_clk_gate("i2c0_gate", "pclk0",
>> + clk_base + REG_CLK_APBCLK1, 0);
>> + hws[I2C1_GATE] = ma35d1_clk_gate("i2c1_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK1, 1);
>> + hws[I2C2_GATE] = ma35d1_clk_gate("i2c2_gate", "pclk2",
>> + clk_base + REG_CLK_APBCLK1, 2);
>> + hws[I2C3_GATE] = ma35d1_clk_gate("i2c3_gate", "pclk0",
>> + clk_base + REG_CLK_APBCLK1, 3);
>> + hws[I2C4_GATE] = ma35d1_clk_gate("i2c4_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK1, 4);
>> + hws[I2C5_GATE] = ma35d1_clk_gate("i2c5_gate", "pclk2",
>> + clk_base + REG_CLK_APBCLK1, 5);
>> +
>> + /* QSPI0~1 */
>> + hws[QSPI0_MUX] = ma35d1_clk_mux("qspi0_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 8, 2, qspi0_sel_clks,
>> + ARRAY_SIZE(qspi0_sel_clks));
>> + hws[QSPI0_GATE] = ma35d1_clk_gate("qspi0_gate", "qspi0_mux",
>> + clk_base + REG_CLK_APBCLK1, 6);
>> + hws[QSPI1_MUX] = ma35d1_clk_mux("qspi1_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 10, 2, qspi1_sel_clks,
>> + ARRAY_SIZE(qspi1_sel_clks));
>> + hws[QSPI1_GATE] = ma35d1_clk_gate("qspi1_gate", "qspi1_mux",
>> + clk_base + REG_CLK_APBCLK1, 7);
>> +
>> + /* SMC0~1 */
>> + hws[SMC0_MUX] = ma35d1_clk_mux("smc0_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 28, 1, smc_sel_clks,
>> + ARRAY_SIZE(smc_sel_clks));
>> + hws[SMC0_DIV] = ma35d1_clk_divider("smc0_div", "smc0_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 0, 4);
>> + hws[SMC0_GATE] = ma35d1_clk_gate("smc0_gate", "smc0_div",
>> + clk_base + REG_CLK_APBCLK1, 12);
>> +
>> + hws[SMC1_MUX] = ma35d1_clk_mux("smc1_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 29, 1, smc_sel_clks,
>> + ARRAY_SIZE(smc_sel_clks));
>> + hws[SMC1_DIV] = ma35d1_clk_divider("smc1_div", "smc1_mux",
>> + clk_base + REG_CLK_CLKDIV1,
>> + 4, 4);
>> + hws[SMC1_GATE] = ma35d1_clk_gate("smc1_gate", "smc1_div",
>> + clk_base + REG_CLK_APBCLK1, 13);
>> +
>> + /* WDT0~2 */
>> + hws[WDT0_MUX] = ma35d1_clk_mux("wdt0_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 20, 2, wdt0_sel_clks,
>> + ARRAY_SIZE(wdt0_sel_clks));
>> + hws[WDT0_GATE] = ma35d1_clk_gate("wdt0_gate", "wdt0_mux",
>> + clk_base + REG_CLK_APBCLK1, 16);
>> + hws[WDT1_MUX] = ma35d1_clk_mux("wdt1_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 24, 2, wdt1_sel_clks,
>> + ARRAY_SIZE(wdt1_sel_clks));
>> + hws[WDT1_GATE] = ma35d1_clk_gate("wdt1_gate", "wdt1_mux",
>> + clk_base + REG_CLK_APBCLK1, 17);
>> + hws[WDT2_MUX] = ma35d1_clk_mux("wdt2_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 28, 2, wdt2_sel_clks,
>> + ARRAY_SIZE(wdt2_sel_clks));
>> + hws[WDT2_GATE] = ma35d1_clk_gate("wdt2_gate", "wdt2_mux",
>> + clk_base + REG_CLK_APBCLK1, 18);
>> +
>> + /* WWDT0~2 */
>> + hws[WWDT0_MUX] = ma35d1_clk_mux("wwdt0_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 22, 2, wwdt0_sel_clks,
>> + ARRAY_SIZE(wwdt0_sel_clks));
>> + hws[WWDT1_MUX] = ma35d1_clk_mux("wwdt1_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 26, 2, wwdt1_sel_clks,
>> + ARRAY_SIZE(wwdt1_sel_clks));
>> + hws[WWDT2_MUX] = ma35d1_clk_mux("wwdt2_mux",
>> + clk_base + REG_CLK_CLKSEL3,
>> + 30, 2, wwdt2_sel_clks,
>> + ARRAY_SIZE(wwdt2_sel_clks));
>> +
>> + /* EPWM0~2 */
>> + hws[EPWM0_GATE] = ma35d1_clk_gate("epwm0_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK1, 24);
>> + hws[EPWM1_GATE] = ma35d1_clk_gate("epwm1_gate", "pclk2",
>> + clk_base + REG_CLK_APBCLK1, 25);
>> + hws[EPWM2_GATE] = ma35d1_clk_gate("epwm2_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK1, 26);
>> +
>> + /* I2S0~1 */
>> + hws[I2S0_MUX] = ma35d1_clk_mux("i2s0_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 12, 2, i2s0_sel_clks,
>> + ARRAY_SIZE(i2s0_sel_clks));
>> + hws[I2S0_GATE] = ma35d1_clk_gate("i2s0_gate", "i2s0_mux",
>> + clk_base + REG_CLK_APBCLK2, 0);
>> + hws[I2S1_MUX] = ma35d1_clk_mux("i2s1_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 14, 2, i2s1_sel_clks,
>> + ARRAY_SIZE(i2s1_sel_clks));
>> + hws[I2S1_GATE] = ma35d1_clk_gate("i2s1_gate", "i2s1_mux",
>> + clk_base + REG_CLK_APBCLK2, 1);
>> +
>> + /* SSMCC */
>> + hws[SSMCC_GATE] = ma35d1_clk_gate("ssmcc_gate", "pclk3",
>> + clk_base + REG_CLK_APBCLK2, 2);
>> +
>> + /* SSPCC */
>> + hws[SSPCC_GATE] = ma35d1_clk_gate("sspcc_gate", "pclk3",
>> + clk_base + REG_CLK_APBCLK2, 3);
>> +
>> + /* SPI0~3 */
>> + hws[SPI0_MUX] = ma35d1_clk_mux("spi0_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 0, 2, spi0_sel_clks,
>> + ARRAY_SIZE(spi0_sel_clks));
>> + hws[SPI0_GATE] = ma35d1_clk_gate("spi0_gate", "spi0_mux",
>> + clk_base + REG_CLK_APBCLK2, 4);
>> + hws[SPI1_MUX] = ma35d1_clk_mux("spi1_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 2, 2, spi1_sel_clks,
>> + ARRAY_SIZE(spi1_sel_clks));
>> + hws[SPI1_GATE] = ma35d1_clk_gate("spi1_gate", "spi1_mux",
>> + clk_base + REG_CLK_APBCLK2, 5);
>> + hws[SPI2_MUX] = ma35d1_clk_mux("spi2_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 4, 2, spi2_sel_clks,
>> + ARRAY_SIZE(spi2_sel_clks));
>> + hws[SPI2_GATE] = ma35d1_clk_gate("spi2_gate", "spi2_mux",
>> + clk_base + REG_CLK_APBCLK2, 6);
>> + hws[SPI3_MUX] = ma35d1_clk_mux("spi3_mux",
>> + clk_base + REG_CLK_CLKSEL4,
>> + 6, 2, spi3_sel_clks,
>> + ARRAY_SIZE(spi3_sel_clks));
>> + hws[SPI3_GATE] = ma35d1_clk_gate("spi3_gate", "spi3_mux",
>> + clk_base + REG_CLK_APBCLK2, 7);
>> +
>> + /* ECAP0~2 */
>> + hws[ECAP0_GATE] = ma35d1_clk_gate("ecap0_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK2, 8);
>> + hws[ECAP1_GATE] = ma35d1_clk_gate("ecap1_gate", "pclk2",
>> + clk_base + REG_CLK_APBCLK2, 9);
>> + hws[ECAP2_GATE] = ma35d1_clk_gate("ecap2_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK2, 10);
>> +
>> + /* QEI0~2 */
>> + hws[QEI0_GATE] = ma35d1_clk_gate("qei0_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK2, 12);
>> + hws[QEI1_GATE] = ma35d1_clk_gate("qei1_gate", "pclk2",
>> + clk_base + REG_CLK_APBCLK2, 13);
>> + hws[QEI2_GATE] = ma35d1_clk_gate("qei2_gate", "pclk1",
>> + clk_base + REG_CLK_APBCLK2, 14);
>> +
>> + /* ADC */
>> + hws[ADC_DIV] = ma35d1_reg_adc_clkdiv(dev, "adc_div", "pclk0", 0,
>> + clk_base + REG_CLK_CLKDIV4,
>> + 4, 17, 0x1ffff);
>> + hws[ADC_GATE] = ma35d1_clk_gate("adc_gate", "adc_div",
>> + clk_base + REG_CLK_APBCLK2, 24);
>> +
>> + /* EADC */
>> + hws[EADC_DIV] = ma35d1_clk_divider_table("eadc_div", "pclk2",
>> + clk_base + REG_CLK_CLKDIV4,
>> + 0, 4, eadc_div_table);
>> + hws[EADC_GATE] = ma35d1_clk_gate("eadc_gate", "eadc_div",
>> + clk_base + REG_CLK_APBCLK2, 25);
>> +
>> + ret = of_clk_add_hw_provider(clk_node, of_clk_hw_onecell_get,
>> + ma35d1_hw_data);
>> + if (ret < 0) {
>> + dev_err(dev, "failed to register hws for MA35D1\n");
>> + iounmap(clk_base);
>> + }
>> + return ret;
>> +}
>> +
>> +static const struct of_device_id ma35d1_clk_of_match[] = {
>> + { .compatible = "nuvoton,ma35d1-clk" },
>> + { },
>> +};
>> +MODULE_DEVICE_TABLE(of, ma35d1_clk_of_match);
>> +
>> +static struct platform_driver ma35d1_clk_driver = {
>> + .probe = ma35d1_clocks_probe,
>> + .driver = {
>> + .name = "ma35d1-clk",
>> + .of_match_table = ma35d1_clk_of_match,
>> + },
>> +};
>> +
>> +static int __init ma35d1_clocks_init(void)
>> +{
>> + return platform_driver_register(&ma35d1_clk_driver);
>> +}
>> +
>> +postcore_initcall(ma35d1_clocks_init);
>> +
>> +MODULE_AUTHOR("Chi-Fang Li<[email protected]>");
> Space missing.
>
>> +MODULE_DESCRIPTION("NUVOTON MA35D1 Clock Driver");
>> +MODULE_LICENSE("GPL v2");
> "GPL" is enough.
OK
>
>> diff --git a/drivers/clk/nuvoton/clk-ma35d1.h b/drivers/clk/nuvoton/clk-ma35d1.h
>> new file mode 100644
>> index 000000000000..faae5a17e425
>> --- /dev/null
>> +++ b/drivers/clk/nuvoton/clk-ma35d1.h
>> @@ -0,0 +1,198 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#ifndef __DRV_CLK_NUVOTON_MA35D1_H
>> +#define __DRV_CLK_NUVOTON_MA35D1_H
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clkdev.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/regmap.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/mfd/ma35d1-sys.h>
>> +
>> +enum ma35d1_pll_type {
>> + MA35D1_CAPLL,
>> + MA35D1_DDRPLL,
>> + MA35D1_APLL,
>> + MA35D1_EPLL,
>> + MA35D1_VPLL,
>> +};
>> +
>> +enum ma35d1_pll_mode {
>> + VSIPLL_INTEGER_MODE,
>> + VSIPLL_FRACTIONAL_MODE,
>> + VSIPLL_SS_MODE,
>> +};
>> +
>> +/* VSI-PLL CTL0~2 */
>> +#define VSIPLL_CTL0 0x0
>> +#define VSIPLL_CTL1 0x4
>> +#define VSIPLL_CTL2 0x8
>> +
>> +/* VSI-PLL Specification limits */
>> +#define VSIPLL_FREF_MAX_FREQ 200000000UL
>> +#define VSIPLL_FREF_MIN_FREQ 1000000UL
>> +#define VSIPLL_FREFDIVM_MAX_FREQ 40000000UL
>> +#define VSIPLL_FREFDIVM_MIN_FREQ0 1000000UL
>> +#define VSIPLL_FREFDIVM_MIN_FREQ1 10000000UL
>> +#define VSIPLL_FCLK_MAX_FREQ 2400000000UL
>> +#define VSIPLL_FCLK_MIN_FREQ 600000000UL
>> +#define VSIPLL_FCLKO_MAX_FREQ 2400000000UL
>> +#define VSIPLL_FCLKO_MIN_FREQ 85700000UL
>> +#define VSIPLL_SPREAD_RANGE 194
>> +#define VSIPLL_MODULATION_FREQ 50000
>> +
>> +/* Clock Control Registers Offset */
>> +#define REG_CLK_PWRCTL (0x00)
> Unnecessary parenthesis.
>
>> +#define REG_CLK_SYSCLK0 (0x04)
>> +#define REG_CLK_SYSCLK1 (0x08)
>> +#define REG_CLK_APBCLK0 (0x0C)
>> +#define REG_CLK_APBCLK1 (0x10)
>> +#define REG_CLK_APBCLK2 (0x14)
>> +#define REG_CLK_CLKSEL0 (0x18)
>> +#define REG_CLK_CLKSEL1 (0x1C)
>> +#define REG_CLK_CLKSEL2 (0x20)
>> +#define REG_CLK_CLKSEL3 (0x24)
>> +#define REG_CLK_CLKSEL4 (0x28)
>> +#define REG_CLK_CLKDIV0 (0x2C)
>> +#define REG_CLK_CLKDIV1 (0x30)
>> +#define REG_CLK_CLKDIV2 (0x34)
>> +#define REG_CLK_CLKDIV3 (0x38)
>> +#define REG_CLK_CLKDIV4 (0x3C)
>> +#define REG_CLK_CLKOCTL (0x40)
>> +#define REG_CLK_STATUS (0x50)
>> +#define REG_CLK_PLL0CTL0 (0x60)
>> +#define REG_CLK_PLL2CTL0 (0x80)
>> +#define REG_CLK_PLL2CTL1 (0x84)
>> +#define REG_CLK_PLL2CTL2 (0x88)
>> +#define REG_CLK_PLL3CTL0 (0x90)
>> +#define REG_CLK_PLL3CTL1 (0x94)
>> +#define REG_CLK_PLL3CTL2 (0x98)
>> +#define REG_CLK_PLL4CTL0 (0xA0)
>> +#define REG_CLK_PLL4CTL1 (0xA4)
>> +#define REG_CLK_PLL4CTL2 (0xA8)
>> +#define REG_CLK_PLL5CTL0 (0xB0)
>> +#define REG_CLK_PLL5CTL1 (0xB4)
>> +#define REG_CLK_PLL5CTL2 (0xB8)
>> +#define REG_CLK_CLKDCTL (0xC0)
>> +#define REG_CLK_CLKDSTS (0xC4)
>> +#define REG_CLK_CDUPB (0xC8)
>> +#define REG_CLK_CDLOWB (0xCC)
>> +#define REG_CLK_CKFLTRCTL (0xD0)
>> +#define REG_CLK_TESTCLK (0xF0)
>> +#define REG_CLK_PLLCTL (0x40)
>> +
>> +/* Constant Definitions for Clock Controller */
>> +#define SMICPLLCTL0_FBDIV_POS (0)
>> +#define SMICPLLCTL0_FBDIV_MSK (0xfful << SMICPLLCTL0_FBDIV_POS)
>> +#define SMICPLLCTL0_INDIV_POS (8)
>> +#define SMICPLLCTL0_INDIV_MSK (0xful << SMICPLLCTL0_INDIV_POS)
>> +#define SMICPLLCTL0_OUTDIV_POS (12)
>> +#define SMICPLLCTL0_OUTDIV_MSK (0x3ul << SMICPLLCTL0_OUTDIV_POS)
> GENMASK() + remove _POS define completely.
OK, we will fix them all.
>
>> +#define SMICPLLCTL0_PD_POS (16)
>> +#define SMICPLLCTL0_PD_MSK (0x1ul << SMICPLLCTL0_PD_POS)
> BIT() + remove _POS.
OK, we will fix them all.
> Is this really a mask or a bit? I'd remove _MSK from the name (which is
> usually not that useful anyway even if it would be a multiple bit mask
> for real).
>
>> +#define SMICPLLCTL0_BP_POS (17)
>> +#define SMICPLLCTL0_BP_MSK (0x1ul << SMICPLLCTL0_BP_POS)
> BIT()?
Yes, we will use BIT().
>> +#define VSIPLLCTL0_FBDIV_POS (0)
>> +#define VSIPLLCTL0_FBDIV_MSK (0x7fful << VSIPLLCTL0_FBDIV_POS)
>> +#define VSIPLLCTL0_INDIV_POS (12)
>> +#define VSIPLLCTL0_INDIV_MSK (0x3ful << VSIPLLCTL0_INDIV_POS)
>> +#define VSIPLLCTL0_MODE_POS (18)
>> +#define VSIPLLCTL0_MODE_MSK (0x3ul << VSIPLLCTL0_MODE_POS)
>> +#define VSIPLLCTL0_SSRATE_POS (20)
>> +#define VSIPLLCTL0_SSRATE_MSK (0x7fful << VSIPLLCTL0_SSRATE_POS)
>> +#define VSIPLLCTL1_PD_POS (0)
>> +#define VSIPLLCTL1_PD_MSK (0x1ul << VSIPLLCTL1_PD_POS)
>> +#define VSIPLLCTL1_BP_POS (1)
>> +#define VSIPLLCTL1_BP_MSK (0x1ul << VSIPLLCTL1_BP_POS)
>> +#define VSIPLLCTL1_OUTDIV_POS (4)
>> +#define VSIPLLCTL1_OUTDIV_MSK (0x7ul << VSIPLLCTL1_OUTDIV_POS)
>> +#define VSIPLLCTL1_FRAC_POS (8)
>> +#define VSIPLLCTL1_FRAC_MSK (0xfffffful << VSIPLLCTL1_FRAC_POS)
>> +#define VSIPLLCTL2_SLOPE_POS (0)
>> +#define VSIPLLCTL2_SLOPE_MSK (0xfffffful << VSIPLLCTL2_SLOPE_POS)
> ...and more of them.
>
>
Best regards,
Jacky Huang
On 18/03/2023 05:30, Jacky Huang wrote:
> Dear Krzysztof,
>
>
> Thanks for your advice.
>
>
> On 2023/3/16 下午 03:39, Krzysztof Kozlowski wrote:
>> On 16/03/2023 08:37, Krzysztof Kozlowski wrote:
>>> On 15/03/2023 08:28, Jacky Huang wrote:
>>>> From: Jacky Huang <[email protected]>
>>>>
>>>> Add documentation to describe nuvoton ma35d1 reset driver bindings.
>>> Subject: drop second/last, redundant "bindings". The "dt-bindings"
>>> prefix is already stating that these are bindings.
>
>
> OK, I will fix it.
>
>
>>>> Signed-off-by: Jacky Huang <[email protected]>
>>>> ---
>>>> .../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
>>>> 1 file changed, 50 insertions(+)
>>>> create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>> new file mode 100644
>>>> index 000000000000..f66c566c6dce
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>> @@ -0,0 +1,50 @@
>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Nuvoton MA35D1 Reset Controller
>>>> +
>>>> +maintainers:
>>>> + - Chi-Fang Li <[email protected]>
>>>> + - Jacky Huang <[email protected]>
>>>> +
>>>> +description:
>>>> + The system reset controller can be used to reset various peripheral
>>>> + controllers in MA35D1 SoC.
>>>> +
>>>> +properties:
>>>> + compatible:
>>>> + const: nuvoton,ma35d1-reset
>>>> +
>>>> + regmap:
>>>> + $ref: /schemas/types.yaml#/definitions/phandle
>>>> + description: Phandle to the register map node.
>>> You need to be specific what is this. As you can easily check, there is
>>> no such property in any devices. I don't understand why do you need it
>>> in the first place.
>
> reset: reset-controller {
> compatible = "nuvoton,ma35d1-reset";
> regmap = <&sys>;
> #reset-cells = <1>;
> };
>
> The dt_binding_check check report an error about the above "regmap".
>
> I found that add this can pass the test.
Do not add properties to bindings to "pass the test". That's not the
goal of bindings. Add there properties because they make sense...
Anyway, you did not answer my question at all. So one by one - address them:
1. As you can easily check, there is no such property in any devices.
Explanation: do you see it anywhere in existing bindings?
2. I don't understand why do you need it in the first place.
Explanation: your binding suggest this is not needed. If you think
otherwise, you need to provide rationale.
Best regards,
Krzysztof
On 18/03/2023 07:07, Jacky Huang wrote:
>
>>
>>> + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0x13) |
>>> + IRQ_TYPE_LEVEL_HIGH)>;
>>> + };
>>> +
>>> + uart0:serial@40700000 {
>>> + compatible = "nuvoton,ma35d1-uart";
>>> + reg = <0x0 0x40700000 0x0 0x100>;
>>> + interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
>>> + clocks = <&clk UART0_GATE>;
>>> + status = "okay";
>> Why? Drop the line... or convert it to disabled. Otherwise, why every
>> SoC has serial0 enabled? Is it used internally?
>
>
> uart0 is on all the way since this SoC booting from the MaskROM boot code,
>
> load arm-trusted-firmware, load bootloader, and finally load linux kernel.
>
> uart0 is also the Linux console.
Are you sure? Maybe my board has UART0 disconnected.
Best regards,
Krzysztof
Dear Arnd,
Thanks for your advice.
On 2023/3/16 下午 10:38, Arnd Bergmann wrote:
> On Wed, Mar 15, 2023, at 08:29, Jacky Huang wrote:
>> From: Jacky Huang <[email protected]>
>>
>> Add entry for Nuvton ma35d1 maintainer and files
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> +F: Documentation/devicetree/bindings/*/*nuvoton*
>> +F: arch/arm64/boot/dts/nuvoton/
> This clashes with the existing entry for NPCM, so
> contributors can easily get confused about where
> to send their dts patches.
>
> I don't have a good solution here, but maybe you can
> discuss this with the npcm maintainers (added to Cc)
> to see how they would like to handle this.
>
> For me, the easiest way would be to have a single
> maintainer send me all the patches for both ma35d1
> and npcm, but that may not be practical for you.
All I can do so far is, once we receive a patch for npcm,
forward it to the maintainers of npcm, and the npcm side
does the same.
And I would like to modify it as
+F: arch/arm64/boot/dts/nuvoton/*ma35*
>> +F: drivers/*/*/*ma35d1*
>> +F: drivers/*/*ma35d1*
>> +F: include/dt-bindings/*/*ma35d1*
>> +F: include/linux/mfd/ma35d1-sys.h
> I would replace these with a single line
>
> K: ma35d1
>
> that should have the same effect.
>
> Arnd
It's fine. I will use K: instead. Thank you.
Best regards,
Jacky Huang
Hi Jacky and Arnd
On Sun, 19 Mar 2023 at 14:01, Jacky Huang <[email protected]> wrote:
>
> Dear Arnd,
>
>
> Thanks for your advice.
>
>
> On 2023/3/16 下午 10:38, Arnd Bergmann wrote:
> > On Wed, Mar 15, 2023, at 08:29, Jacky Huang wrote:
> >> From: Jacky Huang <[email protected]>
> >>
> >> Add entry for Nuvton ma35d1 maintainer and files
> >>
> >> Signed-off-by: Jacky Huang <[email protected]>
> >> ---
> >> +F: Documentation/devicetree/bindings/*/*nuvoton*
> >> +F: arch/arm64/boot/dts/nuvoton/
> > This clashes with the existing entry for NPCM, so
> > contributors can easily get confused about where
> > to send their dts patches.
> >
> > I don't have a good solution here, but maybe you can
> > discuss this with the npcm maintainers (added to Cc)
> > to see how they would like to handle this.
> >
> > For me, the easiest way would be to have a single
> > maintainer send me all the patches for both ma35d1
> > and npcm, but that may not be practical for you.
>
>
> All I can do so far is, once we receive a patch for npcm,
>
> forward it to the maintainers of npcm, and the npcm side
We can forward ma35 DTS emails to you as well.
>
> does the same.
>
> And I would like to modify it as
>
> +F: arch/arm64/boot/dts/nuvoton/*ma35*
About modify
F: arch/arm64/boot/dts/nuvoton/
to
F: arch/arm64/boot/dts/nuvoton/*npcm*
We can't guarantee that our customers will use npcm in the dts files name.
>
> >> +F: drivers/*/*/*ma35d1*
> >> +F: drivers/*/*ma35d1*
> >> +F: include/dt-bindings/*/*ma35d1*
> >> +F: include/linux/mfd/ma35d1-sys.h
> > I would replace these with a single line
> >
> > K: ma35d1
> >
> > that should have the same effect.
> >
> > Arnd
>
>
> It's fine. I will use K: instead. Thank you.
>
>
> Best regards,
>
> Jacky Huang
>
>
Thanks,
Tomer
Dear Ilpo,
On 2023/3/16 下午 11:05, Ilpo Järvinen wrote:
> On Wed, 15 Mar 2023, Jacky Huang wrote:
>
>> From: Jacky Huang <[email protected]>
>>
>> This driver supports individual IP reset for ma35d1. The reset
>> control registers is a subset of system control registers.
>>
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> drivers/reset/Kconfig | 6 ++
>> drivers/reset/Makefile | 1 +
>> drivers/reset/reset-ma35d1.c | 152 +++++++++++++++++++++++++++++++++++
>> 3 files changed, 159 insertions(+)
>> create mode 100644 drivers/reset/reset-ma35d1.c
>>
>> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
>> index 2a52c990d4fe..47671060d259 100644
>> --- a/drivers/reset/Kconfig
>> +++ b/drivers/reset/Kconfig
>> @@ -143,6 +143,12 @@ config RESET_NPCM
>> This enables the reset controller driver for Nuvoton NPCM
>> BMC SoCs.
>>
>> +config RESET_NUVOTON_MA35D1
>> + bool "Nuvton MA35D1 Reset Driver"
>> + default ARCH_NUVOTON
>> + help
>> + This enables the reset controller driver for Nuvoton MA35D1 SoC.
>> +
>> config RESET_OXNAS
>> bool
>>
>> diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
>> index 3e7e5fd633a8..fd52dcf66a99 100644
>> --- a/drivers/reset/Makefile
>> +++ b/drivers/reset/Makefile
>> @@ -20,6 +20,7 @@ obj-$(CONFIG_RESET_MCHP_SPARX5) += reset-microchip-sparx5.o
>> obj-$(CONFIG_RESET_MESON) += reset-meson.o
>> obj-$(CONFIG_RESET_MESON_AUDIO_ARB) += reset-meson-audio-arb.o
>> obj-$(CONFIG_RESET_NPCM) += reset-npcm.o
>> +obj-$(CONFIG_RESET_NUVOTON_MA35D1) += reset-ma35d1.o
>> obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
>> obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
>> obj-$(CONFIG_RESET_POLARFIRE_SOC) += reset-mpfs.o
>> diff --git a/drivers/reset/reset-ma35d1.c b/drivers/reset/reset-ma35d1.c
>> new file mode 100644
>> index 000000000000..bdd39483ca4e
>> --- /dev/null
>> +++ b/drivers/reset/reset-ma35d1.c
>> @@ -0,0 +1,152 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + * Author: Chi-Fang Li <[email protected]>
>> + */
>> +
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> +#include <linux/mfd/syscon.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset-controller.h>
>> +#include <linux/mfd/ma35d1-sys.h>
>> +#include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
>> +#include <linux/regmap.h>
>> +#include <linux/reboot.h>
>> +
>> +#define RST_PRE_REG 32
>> +
>> +struct ma35d1_reset_data {
>> + struct reset_controller_dev rcdev;
>> + struct regmap *regmap;
>> +};
>> +
>> +struct ma35d1_reboot_data {
>> + struct notifier_block restart_handler;
>> + struct regmap *regmap;
>> +};
>> +
>> +static int ma35d1_restart_handler(struct notifier_block *this,
>> + unsigned long mode, void *cmd)
>> +{
>> + struct ma35d1_reboot_data *data =
>> + container_of(this, struct ma35d1_reboot_data,
>> + restart_handler);
>> + regmap_write(data->regmap, REG_SYS_IPRST0, 1 << MA35D1_RESET_CHIP);
>> + return -EAGAIN;
> This results -EAGAIN always???
The chip will reset immediately after the write to REG_SYS_IPRST0.
The return line should never be executed. If yes, it must be failed to
reset.
Anyway, I will modify it as
return regmap_write(data->regmap, REG_SYS_IPRST0, 1 << MA35D1_RESET_CHIP);
>
>> +}
>> +
>> +static int ma35d1_reset_update(struct reset_controller_dev *rcdev,
>> + unsigned long id, bool assert)
>> +{
>> + int reg;
>> + int offset = (id / RST_PRE_REG) * 4;
>> + struct ma35d1_reset_data *data =
>> + container_of(rcdev, struct ma35d1_reset_data, rcdev);
>> +
>> + regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
>> + if (assert)
>> + reg |= 1 << (id % RST_PRE_REG);
>> + else
>> + reg &= ~(1 << (id % RST_PRE_REG));
>> +
>> + regmap_write(data->regmap, REG_SYS_IPRST0 + offset, reg);
>> + return 0;
> This returns always 0. What about regmap_read/write() errors, should the
> be returned?
I will modify it as
return regmap_write(data->regmap, REG_SYS_IPRST0 + offset, reg);
And add return value check to the regmap_read().
>> +}
>> +
>> +static int ma35d1_reset_assert(struct reset_controller_dev *rcdev,
>> + unsigned long id)
>> +{
>> + return ma35d1_reset_update(rcdev, id, true);
>> +}
>> +
>> +static int ma35d1_reset_deassert(struct reset_controller_dev *rcdev,
>> + unsigned long id)
>> +{
>> + return ma35d1_reset_update(rcdev, id, false);
>> +}
>> +
>> +static int ma35d1_reset_status(struct reset_controller_dev *rcdev,
>> + unsigned long id)
>> +{
>> + int reg;
>> + int offset = id / RST_PRE_REG;
>> + struct ma35d1_reset_data *data =
>> + container_of(rcdev, struct ma35d1_reset_data, rcdev);
>> +
>> + regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
> Error handling?
I will modify it as
ret = regmap_read(data->regmap, REG_SYS_IPRST0 + offset, ®);
if (ret < 0)
return ret;
>
>> + return !!(reg & BIT(id % RST_PRE_REG));
>> +}
>> +
>> +static const struct reset_control_ops ma35d1_reset_ops = {
>> + .assert = ma35d1_reset_assert,
>> + .deassert = ma35d1_reset_deassert,
>> + .status = ma35d1_reset_status,
>> +};
>> +
>> +static const struct of_device_id ma35d1_reset_dt_ids[] = {
>> + { .compatible = "nuvoton,ma35d1-reset" },
>> + { },
>> +};
>> +
>> +static int ma35d1_reset_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct ma35d1_reset_data *reset_data;
>> + struct ma35d1_reboot_data *reboot_data;
>> + int err;
>> +
>> + if (!pdev->dev.of_node) {
>> + dev_err(&pdev->dev, "Device tree node not found\n");
>> + return -EINVAL;
>> + }
>> +
>> + reset_data = devm_kzalloc(dev, sizeof(*reset_data), GFP_KERNEL);
>> + if (!reset_data)
>> + return -ENOMEM;
>> +
>> + reboot_data = devm_kzalloc(dev, sizeof(*reboot_data), GFP_KERNEL);
>> + if (!reboot_data) {
>> + devm_kfree(dev, reset_data);
> Unnecessary.
OK, I will remove this devm_kfree().
>> + return -ENOMEM;
>> + }
>> +
>> + reset_data->regmap = syscon_regmap_lookup_by_phandle(
>> + pdev->dev.of_node, "regmap");
>> + if (IS_ERR(reset_data->regmap)) {
>> + dev_err(&pdev->dev, "Failed to get SYS register base\n");
>> + err = PTR_ERR(reset_data->regmap);
>> + goto err_out;
>> + }
>> + reset_data->rcdev.owner = THIS_MODULE;
>> + reset_data->rcdev.nr_resets = MA35D1_RESET_COUNT;
>> + reset_data->rcdev.ops = &ma35d1_reset_ops;
>> + reset_data->rcdev.of_node = dev->of_node;
>> +
>> + reboot_data->regmap = reset_data->regmap;
>> + reboot_data->restart_handler.notifier_call = ma35d1_restart_handler;
>> + reboot_data->restart_handler.priority = 192;
>> +
>> + err = register_restart_handler(&reboot_data->restart_handler);
>> + if (err)
>> + dev_warn(&pdev->dev, "failed to register restart handler\n");
>> +
>> + return devm_reset_controller_register(dev, &reset_data->rcdev);
>> +
>> +err_out:
>> + devm_kfree(dev, reset_data);
>> + devm_kfree(dev, reboot_data);
> These are unnecessary since the probe is failing.
OK, I will make it just return err.
>
>> + return err;
>> +}
>> +
>> +static struct platform_driver ma35d1_reset_driver = {
>> + .probe = ma35d1_reset_probe,
>> + .driver = {
>> + .name = "ma35d1-reset",
>> + .of_match_table = ma35d1_reset_dt_ids,
>> + },
>> +};
>> +
>> +builtin_platform_driver(ma35d1_reset_driver);
>>
Best regards,
Jacky Huang
Dear Krzyszto,
On 2023/3/19 下午 07:06, Krzysztof Kozlowski wrote:
> On 18/03/2023 07:07, Jacky Huang wrote:
>>>> + interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0x13) |
>>>> + IRQ_TYPE_LEVEL_HIGH)>;
>>>> + };
>>>> +
>>>> + uart0:serial@40700000 {
>>>> + compatible = "nuvoton,ma35d1-uart";
>>>> + reg = <0x0 0x40700000 0x0 0x100>;
>>>> + interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
>>>> + clocks = <&clk UART0_GATE>;
>>>> + status = "okay";
>>> Why? Drop the line... or convert it to disabled. Otherwise, why every
>>> SoC has serial0 enabled? Is it used internally?
>>
>> uart0 is on all the way since this SoC booting from the MaskROM boot code,
>>
>> load arm-trusted-firmware, load bootloader, and finally load linux kernel.
>>
>> uart0 is also the Linux console.
> Are you sure? Maybe my board has UART0 disconnected.
>
> Best regards,
> Krzysztof
OK, I will have the uart0 disabled in dtsi, and enabled it in dts.
Best regards,
Jacky Huang
On 2023/3/19 下午 07:05, Krzysztof Kozlowski wrote:
> On 18/03/2023 05:30, Jacky Huang wrote:
>> Dear Krzysztof,
>>
>>
>> Thanks for your advice.
>>
>>
>> On 2023/3/16 下午 03:39, Krzysztof Kozlowski wrote:
>>> On 16/03/2023 08:37, Krzysztof Kozlowski wrote:
>>>> On 15/03/2023 08:28, Jacky Huang wrote:
>>>>> From: Jacky Huang <[email protected]>
>>>>>
>>>>> Add documentation to describe nuvoton ma35d1 reset driver bindings.
>>>> Subject: drop second/last, redundant "bindings". The "dt-bindings"
>>>> prefix is already stating that these are bindings.
>>
>> OK, I will fix it.
>>
>>
>>>>> Signed-off-by: Jacky Huang <[email protected]>
>>>>> ---
>>>>> .../bindings/reset/nuvoton,ma35d1-reset.yaml | 50 +++++++++++++++++++
>>>>> 1 file changed, 50 insertions(+)
>>>>> create mode 100644 Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>>> new file mode 100644
>>>>> index 000000000000..f66c566c6dce
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/reset/nuvoton,ma35d1-reset.yaml
>>>>> @@ -0,0 +1,50 @@
>>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>>> +%YAML 1.2
>>>>> +---
>>>>> +$id: http://devicetree.org/schemas/reset/nuvoton,ma35d1-reset.yaml#
>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>>> +
>>>>> +title: Nuvoton MA35D1 Reset Controller
>>>>> +
>>>>> +maintainers:
>>>>> + - Chi-Fang Li <[email protected]>
>>>>> + - Jacky Huang <[email protected]>
>>>>> +
>>>>> +description:
>>>>> + The system reset controller can be used to reset various peripheral
>>>>> + controllers in MA35D1 SoC.
>>>>> +
>>>>> +properties:
>>>>> + compatible:
>>>>> + const: nuvoton,ma35d1-reset
>>>>> +
>>>>> + regmap:
>>>>> + $ref: /schemas/types.yaml#/definitions/phandle
>>>>> + description: Phandle to the register map node.
>>>> You need to be specific what is this. As you can easily check, there is
>>>> no such property in any devices. I don't understand why do you need it
>>>> in the first place.
>> reset: reset-controller {
>> compatible = "nuvoton,ma35d1-reset";
>> regmap = <&sys>;
>> #reset-cells = <1>;
>> };
>>
>> The dt_binding_check check report an error about the above "regmap".
>>
>> I found that add this can pass the test.
> Do not add properties to bindings to "pass the test". That's not the
> goal of bindings. Add there properties because they make sense...
>
> Anyway, you did not answer my question at all. So one by one - address them:
> 1. As you can easily check, there is no such property in any devices.
> Explanation: do you see it anywhere in existing bindings?
Yes, I cannot find it in all bindings. I know it's wrong.
> 2. I don't understand why do you need it in the first place.
> Explanation: your binding suggest this is not needed. If you think
> otherwise, you need to provide rationale.
>
>
>
> Best regards,
> Krzysztof
>
Now we have removed regmap and modify the dtsi as:
sys: system-management@40460000 {
compatible = "nuvoton,ma35d1-sys", "syscon", "simple-mfd";
reg = <0x0 0x40460000 0x0 0x200>;
reset: reset-controller {
compatible = "nuvoton,ma35d1-reset";
#reset-cells = <1>;
};
};
In the reset driver, we obtain the regmap by parent node:
parent = of_get_parent(dev->of_node); /* parent should be syscon
node */
reset_data->regmap = syscon_node_to_regmap(parent);
of_node_put(parent);
We have it tested OK on ma35d1 SOM board.
And it pass the dt_binding_check and dtbs_check.
Best regards,
Jacky Huang
Dear Ilpo,
Thanks for your advice.
On 2023/3/16 下午 10:54, Ilpo Järvinen wrote:
> Hi,
>
> I'll not note all things below because others have already seemingly
> commented many things.
>
> On Wed, 15 Mar 2023, Jacky Huang wrote:
>
>> From: Jacky Huang <[email protected]>
>>
>> This adds UART and console driver for Nuvoton ma35d1 Soc.
>>
>> MA35D1 SoC provides up to 17 UART controllers, each with one uart port.
>> The ma35d1 uart controller is not compatible with 8250.
>> The uart controller supports:
>> - Full-duplex asynchronous communications
>> - Separates tx and tx 32/32 bytes entry FIFO for data payloads
>> - Hardware auto-flow control
>> - Programmable rx buffer trigger level (1/4/8/14/30 bytes)
>> - Individual programmable baud rate generator for each channel
>> - Supports nCTS, incoming data, rx FIFO reached threshold and
>> RS-485 Address Match (AAD mode) wake-up function
>> - Supports 8-bit rx buffer time-out detection function
>> - Programmable tx data delay time
>> - Supports Auto-Baud Rate measurement and baud rate compensation
>> - Supports break error, frame error, parity error and rx/tx buffer
>> overflow detection function
>> – Programmable number of data bit, 5-, 6-, 7-, 8- bit character
>> – Programmable parity bit, even, odd, no parity or stick parity bit
>> generation and detection
>> – Programmable stop bit, 1, 1.5, or 2 stop bit generation
>> - Supports IrDA SIR function mode
>> - Supports RS-485 function mode
>> – Supports RS-485 9-bit mode
>> – Supports hardware or software enables to program nRTS pin to control
>> RS-485 transmission direction
>> - Supports PDMA transfer function
>> - Support Single-wire function mode.
> This list is probably copy-pasted from somewhere but it doesn't match what
> you implemented in the driver.
I will remove them and rewrite the descriptions.
>> Signed-off-by: Jacky Huang <[email protected]>
>> ---
>> drivers/tty/serial/Kconfig | 18 +
>> drivers/tty/serial/Makefile | 1 +
>> drivers/tty/serial/ma35d1_serial.c | 842 +++++++++++++++++++++++++++++
>> drivers/tty/serial/ma35d1_serial.h | 93 ++++
>> include/uapi/linux/serial_core.h | 3 +
>> 5 files changed, 957 insertions(+)
>> create mode 100644 drivers/tty/serial/ma35d1_serial.c
>> create mode 100644 drivers/tty/serial/ma35d1_serial.h
>>
>> diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
>> index 625358f44419..cb47fe804595 100644
>> --- a/drivers/tty/serial/Kconfig
>> +++ b/drivers/tty/serial/Kconfig
>> @@ -1562,6 +1562,24 @@ config SERIAL_SUNPLUS_CONSOLE
>> you can alter that using a kernel command line option such as
>> "console=ttySUPx".
>>
>> +config SERIAL_NUVOTON_MA35D1
>> + tristate "Nuvoton MA35D1 family UART support"
>> + depends on ARCH_NUVOTON || COMPILE_TEST
>> + select SERIAL_CORE
>> + help
>> + This driver supports Nuvoton MA35D1 family UART ports. If you would
>> + like to use them, you must answer Y or M to this option. Note that
>> + for use as console, it must be included in kernel and not as a
>> + module
>> +
>> +config SERIAL_NUVOTON_MA35D1_CONSOLE
>> + bool "Console on a Nuvotn MA35D1 family UART port"
>> + depends on SERIAL_NUVOTON_MA35D1=y
>> + select SERIAL_CORE_CONSOLE
>> + help
>> + Select this options if you'd like to use the UART port0 of the
>> + Nuvoton MA35D1 family as a console.
>> +
>> endmenu
>>
>> config SERIAL_MCTRL_GPIO
>> diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
>> index cd9afd9e3018..71ebeba06ff2 100644
>> --- a/drivers/tty/serial/Makefile
>> +++ b/drivers/tty/serial/Makefile
>> @@ -93,3 +93,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
>>
>> obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
>> obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
>> +obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
>> diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c
>> new file mode 100644
>> index 000000000000..8940d07c3777
>> --- /dev/null
>> +++ b/drivers/tty/serial/ma35d1_serial.c
>> @@ -0,0 +1,842 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * MA35D1 serial driver
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/moduleparam.h>
>> +#include <linux/ioport.h>
>> +#include <linux/init.h>
>> +#include <linux/console.h>
>> +#include <linux/sysrq.h>
>> +#include <linux/delay.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/tty.h>
>> +#include <linux/tty_flip.h>
>> +#include <linux/clk.h>
>> +#include <linux/serial_reg.h>
>> +#include <linux/serial_core.h>
>> +#include <linux/serial.h>
>> +#include <linux/nmi.h>
>> +#include <linux/mutex.h>
>> +#include <linux/slab.h>
>> +#include <linux/uaccess.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_address.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/io.h>
>> +#include <asm/irq.h>
>> +#include <asm/serial.h>
>> +#include "ma35d1_serial.h"
>> +
>> +#define UART_NR 17
>> +
>> +static struct uart_driver ma35d1serial_reg;
>> +struct clk *clk;
>> +
>> +struct uart_ma35d1_port {
>> + struct uart_port port;
>> + u16 capabilities; /* port capabilities */
>> + u8 ier;
>> + u8 lcr;
>> + u8 mcr;
>> + u8 mcr_mask; /* mask of user bits */
>> + u8 mcr_force; /* mask of forced bits */
>> + struct serial_rs485 rs485; /* rs485 settings */
>> + u32 baud_rate;
>> + int rx_count;
>> + u32 console_baud_rate;
>> + u32 console_line;
>> + u32 console_int;
>> +};
>> +
>> +static struct device_node *ma35d1serial_uart_nodes[UART_NR];
>> +static struct uart_ma35d1_port ma35d1serial_ports[UART_NR] = { 0 };
>> +static void __stop_tx(struct uart_ma35d1_port *p);
>> +static void transmit_chars(struct uart_ma35d1_port *up);
> Try to rearrange such that forward declarations are not necessary for
> functions.
OK, we will fix it.
>
>> +static struct uart_ma35d1_port *to_ma35d1_uart_port(struct uart_port *uart)
> static inline
>
>> +{
>> + return container_of(uart, struct uart_ma35d1_port, port);
>> +}
>> +
>> +static u32 serial_in(struct uart_ma35d1_port *p, int offset)
>> +{
>> + return __raw_readl(p->port.membase + offset);
>> +}
>> +
>> +static void serial_out(struct uart_ma35d1_port *p, int offset, int value)
>> +{
>> + __raw_writel(value, p->port.membase + offset);
>> +}
>> +
>> +static void __stop_tx(struct uart_ma35d1_port *p)
>> +{
>> + u32 ier;
>> +
>> + ier = serial_in(p, UART_REG_IER);
>> + if (ier & THRE_IEN)
>> + serial_out(p, UART_REG_IER, ier & ~THRE_IEN);
>> +}
>> +
>> +static void ma35d1serial_stop_tx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + __stop_tx(up);
>> +}
>> +
>> +static void ma35d1serial_start_tx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 ier;
>> + struct circ_buf *xmit = &up->port.state->xmit;
>> +
>> + ier = serial_in(up, UART_REG_IER);
>> + serial_out(up, UART_REG_IER, ier & ~THRE_IEN);
>> + if (uart_circ_chars_pending(xmit) <
>> + (16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0x3F)))
>> + transmit_chars(up);
>> + serial_out(up, UART_REG_IER, ier | THRE_IEN);
>> +}
>> +
>> +static void ma35d1serial_stop_rx(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + serial_out(up, UART_REG_IER, serial_in(up, UART_REG_IER) & ~RDA_IEN);
>> +}
>> +
>> +static void
>> +receive_chars(struct uart_ma35d1_port *up)
>> +{
>> + u8 ch;
>> + u32 fsr;
>> + u32 isr;
>> + u32 dcnt;
>> + char flag;
>> +
>> + isr = serial_in(up, UART_REG_ISR);
>> + fsr = serial_in(up, UART_REG_FSR);
>> +
>> + while (!(fsr & RX_EMPTY)) {
>> + flag = TTY_NORMAL;
>> + up->port.icount.rx++;
>> +
>> + if (unlikely(fsr & (BIF | FEF | PEF | RX_OVER_IF))) {
>> + if (fsr & BIF) {
>> + serial_out(up, UART_REG_FSR, BIF);
>> + up->port.icount.brk++;
>> + if (uart_handle_break(&up->port))
>> + continue;
>> + }
>> + if (fsr & FEF) {
>> + serial_out(up, UART_REG_FSR, FEF);
>> + up->port.icount.frame++;
>> + }
>> + if (fsr & PEF) {
>> + serial_out(up, UART_REG_FSR, PEF);
>> + up->port.icount.parity++;
>> + }
>> + if (fsr & RX_OVER_IF) {
>> + serial_out(up, UART_REG_FSR, RX_OVER_IF);
>> + up->port.icount.overrun++;
>> + }
> Do you need to write each of those individually to clear(?) them or could
> you just do one write here after the accounting is done:
> serial_out(up, UART_REG_FSR, fsr & (BIF|FEF|PEF|RX_OVER_IF));
> ?
OK, we will modify it clear flags together as possible.
> Also, add some driver specific prefix for the flag naming (all driver
> specific ones, not just these if you have others besides these).
>
We will fix it.
>> + if (fsr & BIF)
>> + flag = TTY_BREAK;
>> + if (fsr & PEF)
>> + flag = TTY_PARITY;
>> + if (fsr & FEF)
>> + flag = TTY_FRAME;
> Are you sure this is the right prioritization or do you perhaps want else
> ifs like in some other serial drivers?
We will modify it as:
if (fsr & BIF)
flag = TTY_BREAK;
else if (fsr & PEF)
flag = TTY_PARITY;
else if (fsr & FEF)
flag = TTY_FRAME;
>> + }
>> + ch = (u8)serial_in(up, UART_REG_RBR);
> Drop the case.
I will fix it.
>> + if (uart_handle_sysrq_char(&up->port, ch))
>> + continue;
>> +
>> + uart_insert_char(&up->port, fsr, RX_OVER_IF, ch, flag);
>> + up->rx_count++;
>> + dcnt = (serial_in(up, UART_REG_FSR) >> 8) & 0x3f;
>> + if (up->rx_count > 1023) {
>> + spin_lock(&up->port.lock);
>> + tty_flip_buffer_push(&up->port.state->port);
>> + spin_unlock(&up->port.lock);
>> + up->rx_count = 0;
> Why is all this ->rx_count trickery necessary? What's so special with the
> size in question?
That is the tricky inherited from the previous arm9 project.
We will remove it.
>> + if ((isr & RXTO_IF) && (dcnt == 0))
>> + goto tout_end;
>> + }
>> + if (isr & RDA_IF) {
>> + if (dcnt == 1)
> Merge to the same comdition.
>
> dcnt could probably have a more descriptive name.
OK, We will fix it.
>> + return;
>> + }
>> + fsr = serial_in(up, UART_REG_FSR);
>> + }
>> + spin_lock(&up->port.lock);
>> + tty_flip_buffer_push(&up->port.state->port);
>> + spin_unlock(&up->port.lock);
>> +tout_end:
>> + up->rx_count = 0;
>> +}
>> +
>> +static void transmit_chars(struct uart_ma35d1_port *up)
>> +{
>> + struct circ_buf *xmit = &up->port.state->xmit;
>> + int count = 16 - ((serial_in(up, UART_REG_FSR) >> 16) & 0xF);
>> +
>> + if (serial_in(up, UART_REG_FSR) & TX_FULL)
>> + count = 0;
>> + if (up->port.x_char) {
>> + serial_out(up, UART_REG_THR, up->port.x_char);
>> + up->port.icount.tx++;
>> + up->port.x_char = 0;
>> + return;
>> + }
>> + if (uart_tx_stopped(&up->port)) {
>> + ma35d1serial_stop_tx(&up->port);
>> + return;
>> + }
>> + if (uart_circ_empty(xmit)) {
>> + __stop_tx(up);
>> + return;
>> + }
>> + while (count > 0) {
>> + serial_out(up, UART_REG_THR, xmit->buf[xmit->tail]);
>> + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
>> + up->port.icount.tx++;
>> + count--;
>> + if (uart_circ_empty(xmit))
>> + break;
>> + }
>> + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
>> + uart_write_wakeup(&up->port);
>> + if (uart_circ_empty(xmit))
>> + __stop_tx(up);
>> +}
>> +
>> +static irqreturn_t ma35d1serial_interrupt(int irq, void *dev_id)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)dev_id;
>> + u32 isr, fsr;
>> +
>> + isr = serial_in(up, UART_REG_ISR);
>> + fsr = serial_in(up, UART_REG_FSR);
>> + if (isr & (RDA_IF | RXTO_IF))
>> + receive_chars(up);
>> + if (isr & THRE_INT)
>> + transmit_chars(up);
>> + if (fsr & (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF))
>> + serial_out(up, UART_REG_FSR,
>> + (BIF | FEF | PEF | RX_OVER_IF | TX_OVER_IF));
> Hmm... Why write these again here... Didn't receive_chars() already
> clear(?) most of these bits??
Yes, most of these are redundant.
I will fix it as only
if (fsr & TX_OVER_IF))
serial_out(up, UART_REG_FSR, TX_OVER_IF);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static u32 ma35d1serial_tx_empty(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 fsr;
>> +
>> + fsr = serial_in(up, UART_REG_FSR);
>> + return (fsr & (TE_FLAG | TX_EMPTY)) == (TE_FLAG | TX_EMPTY) ?
>> + TIOCSER_TEMT : 0;
>> +}
>> +
>> +static u32 ma35d1serial_get_mctrl(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 status;
>> + u32 ret = 0;
>> +
>> + status = serial_in(up, UART_REG_MSR);
>> + if (!(status & 0x10))
>> + ret |= TIOCM_CTS;
>> + return ret;
>> +}
>> +
>> +static void ma35d1serial_set_mctrl(struct uart_port *port, u32 mctrl)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 mcr = 0;
>> + u32 ier = 0;
>> +
>> + if (mctrl & TIOCM_RTS) {
>> + /* set RTS high level trigger */
>> + mcr = serial_in(up, UART_REG_MCR);
>> + mcr |= 0x200;
>> + mcr &= ~(0x2);
>> + }
>> + if (up->mcr & UART_MCR_AFE) {
>> + /* set RTS high level trigger */
>> + mcr = serial_in(up, UART_REG_MCR);
>> + mcr |= 0x200;
>> + mcr &= ~(0x2);
>> +
>> + /* enable CTS/RTS auto-flow control */
>> + serial_out(up, UART_REG_IER,
>> + (serial_in(up, UART_REG_IER) | (0x3000)));
> Once you have named the bits probably with defines, most of the comments
> such as the one above are rendered redundant and should be dropped.
OK, we will drop these unused comments.
>> +
>> + /* Set hardware flow control */
>> + up->port.flags |= UPF_HARD_FLOW;
>> + } else {
>> + /* disable CTS/RTS auto-flow control */
>> + ier = serial_in(up, UART_REG_IER);
>> + ier &= ~(0x3000);
>> + serial_out(up, UART_REG_IER, ier);
>> +
>> + /* un-set hardware flow control */
>> + up->port.flags &= ~UPF_HARD_FLOW;
>> + }
>> +
>> + /* set CTS high level trigger */
>> + serial_out(up, UART_REG_MSR, (serial_in(up, UART_REG_MSR) | (0x100)));
>> + serial_out(up, UART_REG_MCR, mcr);
>> +}
>> +
>> +static void ma35d1serial_break_ctl(struct uart_port *port, int break_state)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + unsigned long flags;
>> + u32 lcr;
>> +
>> + spin_lock_irqsave(&up->port.lock, flags);
>> + lcr = serial_in(up, UART_REG_LCR);
>> + if (break_state != 0)
>> + lcr |= BCB; /* set break */
>> + else
>> + lcr &= ~BCB; /* clr break */
> While BCB might come from HW naming, a better name for would make these
> very obvious (and the comments unnecessary).
We will rename it and remove comments.
>> + serial_out(up, UART_REG_LCR, lcr);
>> + spin_unlock_irqrestore(&up->port.lock, flags);
>> +}
>> +
>> +static int ma35d1serial_startup(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + struct tty_struct *tty = port->state->port.tty;
>> + int retval;
>> +
>> + /* Reset FIFO */
>> + serial_out(up, UART_REG_FCR, TFR | RFR /* | RX_DIS */);
>> +
>> + /* Clear pending interrupts */
>> + serial_out(up, UART_REG_ISR, 0xFFFFFFFF);
> ~0 is another option.
>
>> +
>> + retval = request_irq(port->irq, ma35d1serial_interrupt, 0,
>> + tty ? tty->name : "ma35d1_serial", port);
> Why such exceptional name trickery?
I will modify it as:
const char *name = to_platform_device(port->dev)->name;
retval = request_irq(port->irq, ma35d1serial_interrupt, 0, name, port);
>
>> + if (retval) {
>> + dev_err(up->port.dev, "request irq failed.\n");
>> + return retval;
>> + }
>> +
>> + /* Now, initialize the UART */
>> + /* FIFO trigger level 4 byte */
>> + /* RTS trigger level 8 bytes */
>> + serial_out(up, UART_REG_FCR,
>> + serial_in(up, UART_REG_FCR) | 0x10 | 0x20000);
>> + serial_out(up, UART_REG_LCR, 0x7); /* 8 bit */
>> + serial_out(up, UART_REG_TOR, 0x40);
>> + serial_out(up, UART_REG_IER,
>> + RTO_IEN | RDA_IEN | TIME_OUT_EN | BUFERR_IEN);
>> + return 0;
>> +}
>> +
>> +static void ma35d1serial_shutdown(struct uart_port *port)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + free_irq(port->irq, port);
>> +
>> + /* Disable interrupts from this port */
>> + serial_out(up, UART_REG_IER, 0);
>> +}
>> +
>> +static u32 ma35d1serial_get_divisor(struct uart_port *port, u32 baud)
>> +{
>> + u32 quot;
>> +
>> + quot = (port->uartclk / baud) - 2;
> Unnecessary parenthesis. (+Somebody already commented about the
> unnecessary variable.)
>
>> + return quot;
>> +}
>> +
>> +static void ma35d1serial_set_termios(struct uart_port *port,
>> + struct ktermios *termios,
>> + const struct ktermios *old)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> + u32 lcr = 0;
>> + unsigned long flags;
>> + u32 baud, quot;
>> +
>> + switch (termios->c_cflag & CSIZE) {
>> + case CS5:
>> + lcr = 0;
>> + break;
>> + case CS6:
>> + lcr |= 1;
>> + break;
>> + case CS7:
>> + lcr |= 2;
>> + break;
>> + case CS8:
>> + default:
>> + lcr |= 3;
>> + break;
>> + }
>> +
>> + if (termios->c_cflag & CSTOPB)
>> + lcr |= NSB;
>> + if (termios->c_cflag & PARENB)
>> + lcr |= PBE;
>> + if (!(termios->c_cflag & PARODD))
>> + lcr |= EPE;
>> + if (termios->c_cflag & CMSPAR)
>> + lcr |= SPE;
>> +
>> + baud = uart_get_baud_rate(port, termios, old, port->uartclk / 0xffff,
>> + port->uartclk / 11);
>> +
>> + quot = ma35d1serial_get_divisor(port, baud);
>> +
>> + /*
>> + * Ok, we're now changing the port state. Do it with
>> + * interrupts disabled.
>> + */
>> + spin_lock_irqsave(&up->port.lock, flags);
>> +
>> + up->port.read_status_mask = RX_OVER_IF;
>> + if (termios->c_iflag & INPCK)
>> + up->port.read_status_mask |= FEF | PEF;
>> + if (termios->c_iflag & (BRKINT | PARMRK))
>> + up->port.read_status_mask |= BIF;
>> +
>> + /*
>> + * Characteres to ignore
>> + */
>> + up->port.ignore_status_mask = 0;
>> + if (termios->c_iflag & IGNPAR)
>> + up->port.ignore_status_mask |= FEF | PEF;
>> + if (termios->c_iflag & IGNBRK) {
>> + up->port.ignore_status_mask |= BIF;
>> + /*
>> + * If we're ignoring parity and break indicators,
>> + * ignore overruns too (for real raw support).
>> + */
>> + if (termios->c_iflag & IGNPAR)
>> + up->port.ignore_status_mask |= RX_OVER_IF;
>> + }
>> + if (termios->c_cflag & CRTSCTS)
>> + up->mcr |= UART_MCR_AFE;
>> + else
>> + up->mcr &= ~UART_MCR_AFE;
>> +
>> + ma35d1serial_set_mctrl(&up->port, up->port.mctrl);
>> + serial_out(up, UART_REG_BAUD, quot | 0x30000000);
>> + serial_out(up, UART_REG_LCR, lcr);
> You need to do uart_update_timeout() in the set_termios function.
We will fix it.
>
>> + spin_unlock_irqrestore(&up->port.lock, flags);
>> +}
>> +
>> +static void ma35d1serial_release_port(struct uart_port *port)
>> +{
>> + iounmap(port->membase);
>> + port->membase = NULL;
>> +}
>> +
>> +static int ma35d1serial_request_port(struct uart_port *port)
>> +{
>> + return 0;
>> +}
>> +
>> +static void ma35d1serial_config_port(struct uart_port *port, int flags)
>> +{
>> + int ret;
>> +
>> + /*
>> + * Find the region that we can probe for. This in turn
>> + * tells us whether we can probe for the type of port.
>> + */
>> + ret = ma35d1serial_request_port(port);
>> + if (ret < 0)
>> + return;
>> + port->type = PORT_MA35D1;
>> +}
>> +
>> +static int ma35d1serial_verify_port(struct uart_port *port,
>> + struct serial_struct *ser)
>> +{
>> + if (ser->type != PORT_UNKNOWN && ser->type != PORT_MA35D1)
>> + return -EINVAL;
>> + return 0;
>> +}
>> +
>> +static const char *ma35d1serial_type(struct uart_port *port)
>> +{
>> + return (port->type == PORT_MA35D1) ? "MA35D1" : NULL;
>> +}
>> +
>> +/* Enable or disable the rs485 support */
>> +static int ma35d1serial_config_rs485(struct uart_port *port,
>> + struct ktermios *termios,
>> + struct serial_rs485 *rs485conf)
>> +{
>> + struct uart_ma35d1_port *p = to_ma35d1_uart_port(port);
>> +
>> + p->rs485 = *rs485conf;
>> +
>> + if (p->rs485.delay_rts_before_send >= 1000)
>> + p->rs485.delay_rts_before_send = 1000;
> Don't do this in driver, the core handles the delay limits. You don't seem
> to be using the value anyway for anything???
>
> Please separate the RS485 support into its own patch.
OK, we will remove RS485 support from this initial patch.
Once this initial patch was merged, we will submit the patch for RS485
support.
>
>> + serial_out(p, UART_FUN_SEL,
>> + (serial_in(p, UART_FUN_SEL) & ~FUN_SEL_MASK));
>> +
>> + if (rs485conf->flags & SER_RS485_ENABLED) {
>> + serial_out(p, UART_FUN_SEL,
>> + (serial_in(p, UART_FUN_SEL) | FUN_SEL_RS485));
> Does this pair of serial_out()s glitch the RS485 line if ->rs485_config()
> is called while RS485 mode is already set?
>
> Why you need to do serial_in() from the UART_FUN_SEL twice?
UART_FUN_SEL (2 bits) definition:
00 - UART function
01 - IrDA function
11 - RS485 function
The first searial_in() is used to clear set as UART function.
The second one is used to set RS485 function if SER_RS485_ENABLED is true.
>> +
>> + if (rs485conf->flags & SER_RS485_RTS_ON_SEND)
>> + serial_out(p, UART_REG_MCR,
>> + (serial_in(p, UART_REG_MCR) & ~0x200));
>> + else
>> + serial_out(p, UART_REG_MCR,
>> + (serial_in(p, UART_REG_MCR) | 0x200));
>> +
>> + /* set auto direction mode */
>> + serial_out(p, UART_REG_ALT_CSR,
>> + (serial_in(p, UART_REG_ALT_CSR) | (1 << 10)));
>> + }
>> + return 0;
>> +}
>> +
>> +static int ma35d1serial_ioctl(struct uart_port *port, u32 cmd, unsigned long arg)
>> +{
>> + switch (cmd) {
>> + default:
>> + return -ENOIOCTLCMD;
>> + }
>> + return 0;
>> +}
>> +
>> +static const struct uart_ops ma35d1serial_ops = {
>> + .tx_empty = ma35d1serial_tx_empty,
>> + .set_mctrl = ma35d1serial_set_mctrl,
>> + .get_mctrl = ma35d1serial_get_mctrl,
>> + .stop_tx = ma35d1serial_stop_tx,
>> + .start_tx = ma35d1serial_start_tx,
>> + .stop_rx = ma35d1serial_stop_rx,
>> + .break_ctl = ma35d1serial_break_ctl,
>> + .startup = ma35d1serial_startup,
>> + .shutdown = ma35d1serial_shutdown,
>> + .set_termios = ma35d1serial_set_termios,
>> + .type = ma35d1serial_type,
>> + .release_port = ma35d1serial_release_port,
>> + .request_port = ma35d1serial_request_port,
>> + .config_port = ma35d1serial_config_port,
>> + .verify_port = ma35d1serial_verify_port,
>> + .ioctl = ma35d1serial_ioctl,
>> +};
>> +
>> +static const struct of_device_id ma35d1_serial_of_match[] = {
>> + { .compatible = "nuvoton,ma35d1-uart" },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, ma35d1_serial_of_match);
>> +
>> +#ifdef CONFIG_SERIAL_NUVOTON_MA35D1_CONSOLE
>> +
>> +static void ma35d1serial_console_putchar(struct uart_port *port,
>> + unsigned char ch)
>> +{
>> + struct uart_ma35d1_port *up = (struct uart_ma35d1_port *)port;
>> +
>> + do {
>> + } while ((serial_in(up, UART_REG_FSR) & TX_FULL));
>> + serial_out(up, UART_REG_THR, ch);
>> +}
>> +
>> +/*
>> + * Print a string to the serial port trying not to disturb
>> + * any possible real use of the port...
>> + *
>> + * The console_lock must be held when we get here.
>> + */
>> +static void ma35d1serial_console_write(struct console *co,
>> + const char *s, u32 count)
>> +{
>> + struct uart_ma35d1_port *up = &ma35d1serial_ports[co->index];
>> + unsigned long flags;
>> + u32 ier;
>> +
>> + local_irq_save(flags);
>> +
>> + /*
>> + * First save the IER then disable the interrupts
>> + */
>> + ier = serial_in(up, UART_REG_IER);
>> + serial_out(up, UART_REG_IER, 0);
>> +
>> + uart_console_write(&up->port, s, count, ma35d1serial_console_putchar);
>> +
>> + /*
>> + * Finally, wait for transmitter to become empty
>> + * and restore the IER
>> + */
>> + do {
>> + } while (!(serial_in(up, UART_REG_FSR) & TX_EMPTY));
>> + serial_out(up, UART_REG_IER, ier);
>> + local_irq_restore(flags);
>> +}
>> +
>> +static int __init ma35d1serial_console_setup(struct console *co,
>> + char *options)
>> +{
>> + struct device_node *np = ma35d1serial_uart_nodes[co->index];
>> + struct uart_ma35d1_port *p = &ma35d1serial_ports[co->index];
>> + u32 val32[4];
>> + struct uart_port *port;
>> + int baud = 115200;
>> + int bits = 8;
>> + int parity = 'n';
>> + int flow = 'n';
>> +
>> + /*
>> + * Check whether an invalid uart number has been specified, and
>> + * if so, search for the first available port that does have
>> + * console support.
>> + */
>> + if ((co->index < 0) || (co->index >= UART_NR)) {
>> + pr_debug("Console Port%x out of range\n", co->index);
>> + return -EINVAL;
>> + }
>> +
>> + if (of_property_read_u32_array(np, "reg", val32, 4) != 0)
>> + return -EINVAL;
>> + p->port.iobase = val32[1];
>> + p->port.membase = ioremap(p->port.iobase, 0x10000);
>> + p->port.ops = &ma35d1serial_ops;
>> + p->port.line = 0;
>> + p->port.uartclk = 24000000;
>> +
>> + port = &ma35d1serial_ports[co->index].port;
>> + return uart_set_options(port, co, baud, parity, bits, flow);
>> +}
>> +
>> +static struct console ma35d1serial_console = {
>> + .name = "ttyS",
>> + .write = ma35d1serial_console_write,
>> + .device = uart_console_device,
>> + .setup = ma35d1serial_console_setup,
>> + .flags = CON_PRINTBUFFER | CON_ENABLED,
>> + .index = -1,
>> + .data = &ma35d1serial_reg,
>> +};
>> +
>> +static void
>> +ma35d1serial_console_init_port(void)
>> +{
>> + int i = 0;
>> + struct device_node *np;
>> +
>> + for_each_matching_node(np, ma35d1_serial_of_match) {
>> + if (ma35d1serial_uart_nodes[i] == NULL) {
>> + ma35d1serial_uart_nodes[i] = np;
>> + i++;
>> + }
>> + }
>> +}
>> +
>> +static int __init ma35d1serial_console_init(void)
>> +{
>> + ma35d1serial_console_init_port();
>> + register_console(&ma35d1serial_console);
>> + return 0;
>> +}
>> +console_initcall(ma35d1serial_console_init);
>> +
>> +#define MA35D1SERIAL_CONSOLE (&ma35d1serial_console)
>> +#else
>> +#define MA35D1SERIAL_CONSOLE NULL
>> +#endif
>> +
>> +static struct uart_driver ma35d1serial_reg = {
>> + .owner = THIS_MODULE,
>> + .driver_name = "serial",
>> + .dev_name = "ttyS",
>> + .major = TTY_MAJOR,
>> + .minor = 64,
>> + .cons = MA35D1SERIAL_CONSOLE,
>> + .nr = UART_NR,
>> +};
>> +
>> +/**
>> + * Suspend one serial port.
>> + */
>> +void ma35d1serial_suspend_port(int line)
>> +{
>> + uart_suspend_port(&ma35d1serial_reg, &ma35d1serial_ports[line].port);
>> +}
>> +EXPORT_SYMBOL(ma35d1serial_suspend_port);
>> +
>> +/**
>> + * Resume one serial port.
>> + */
>> +void ma35d1serial_resume_port(int line)
>> +{
>> + struct uart_ma35d1_port *up = &ma35d1serial_ports[line];
>> +
>> + uart_resume_port(&ma35d1serial_reg, &up->port);
>> +}
>> +EXPORT_SYMBOL(ma35d1serial_resume_port);
>> +
>> +/*
>> + * Register a set of serial devices attached to a platform device.
>> + * The list is terminated with a zero flags entry, which means we expect
>> + * all entries to have at least UPF_BOOT_AUTOCONF set.
>> + */
>> +static int ma35d1serial_probe(struct platform_device *pdev)
>> +{
>> + struct resource *res_mem;
>> + struct uart_ma35d1_port *up;
>> + int ret;
>> + struct clk *clk;
>> + int err;
>> +
>> + if (pdev->dev.of_node) {
>> + ret = of_alias_get_id(pdev->dev.of_node, "serial");
>> + if (ret < 0) {
>> + dev_err(&pdev->dev,
>> + "failed to get alias/pdev id, errno %d\n",
>> + ret);
> Just put error prints to one line if you don't break 100 chars limit.
But the checkpatch limitation is 80 characters.
>> + return ret;
> Misaligned line.
will be fixed
>
>> + }
>> + }
>> + up = &ma35d1serial_ports[ret];
>> + up->port.line = ret;
>> + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!res_mem)
>> + return -ENODEV;
>> +
>> + up->port.iobase = res_mem->start;
>> + up->port.membase = ioremap(up->port.iobase, 0x10000);
> Define for the literal.
OK, we will have #define for it.
>> + up->port.ops = &ma35d1serial_ops;
>> +
>> + spin_lock_init(&up->port.lock);
>> +
>> + clk = of_clk_get(pdev->dev.of_node, 0);
>> + if (IS_ERR(clk)) {
>> + err = PTR_ERR(clk);
>> + dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
>> + return -ENOENT;
>> + }
>> + err = clk_prepare_enable(clk);
>> + if (err)
>> + return -ENOENT;
>> +
>> + if (up->port.line != 0)
>> + up->port.uartclk = clk_get_rate(clk);
>> + up->port.irq = platform_get_irq(pdev, 0);
>> + up->port.dev = &pdev->dev;
>> + up->port.flags = UPF_BOOT_AUTOCONF;
>> + up->port.rs485_config = ma35d1serial_config_rs485;
> Please provide also .rs485_supported for the serial core to handle the
> supported features for you.
OK, we will add it.
>> + ret = uart_add_one_port(&ma35d1serial_reg, &up->port);
>> + platform_set_drvdata(pdev, up);
>> + return 0;
>> +}
>> +
>> +/*
>> + * Remove serial ports registered against a platform device.
>> + */
>> +static int ma35d1serial_remove(struct platform_device *dev)
>> +{
>> + int i;
>> + struct uart_port *port = platform_get_drvdata(dev);
>> +
>> + free_irq(port->irq, port);
>> + for (i = 0; i < UART_NR; i++) {
>> + struct uart_ma35d1_port *up = &ma35d1serial_ports[i];
>> +
>> + if (up->port.dev == &dev->dev)
>> + uart_remove_one_port(&ma35d1serial_reg, &up->port);
>> + }
>> + return 0;
>> +}
>> +
>> +static int ma35d1serial_suspend(struct platform_device *dev,
>> + pm_message_t state)
>> +{
>> + int i;
>> + struct uart_ma35d1_port *up;
>> +
>> + if (dev->dev.of_node)
>> + i = of_alias_get_id(dev->dev.of_node, "serial");
>> + if (i < 0) {
>> + dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
>> + i);
> Just put this to the same line with the rest.
>
>> + return i;
>> + }
>> + up = &ma35d1serial_ports[i];
>> + if (i == 0) {
>> + up->console_baud_rate = serial_in(up, UART_REG_BAUD);
>> + up->console_line = serial_in(up, UART_REG_LCR);
>> + up->console_int = serial_in(up, UART_REG_IER);
>> + }
>> + return 0;
>> +}
>> +
>> +static int ma35d1serial_resume(struct platform_device *dev)
>> +{
>> + int i;
>> + struct uart_ma35d1_port *up;
>> +
>> + if (dev->dev.of_node)
>> + i = of_alias_get_id(dev->dev.of_node, "serial");
>> + if (i < 0) {
>> + dev_err(&dev->dev, "failed to get alias/pdev id, errno %d\n",
>> + i);
> Same line.
will be fixed
>
>> + return i;
>> + }
>> + up = &ma35d1serial_ports[i];
>> + if (i == 0) {
>> + serial_out(up, UART_REG_BAUD, up->console_baud_rate);
>> + serial_out(up, UART_REG_LCR, up->console_line);
>> + serial_out(up, UART_REG_IER, up->console_int);
>> + }
>> + return 0;
>> +}
>> +
>> +static struct platform_driver ma35d1serial_driver = {
>> + .probe = ma35d1serial_probe,
>> + .remove = ma35d1serial_remove,
>> + .suspend = ma35d1serial_suspend,
>> + .resume = ma35d1serial_resume,
>> + .driver = {
>> + .name = "ma35d1-uart",
>> + .owner = THIS_MODULE,
>> + .of_match_table = of_match_ptr(ma35d1_serial_of_match),
>> + },
>> +};
>> +
>> +static int __init ma35d1serial_init(void)
>> +{
>> + int ret;
>> +
>> + ret = uart_register_driver(&ma35d1serial_reg);
>> + if (ret)
>> + return ret;
>> + ret = platform_driver_register(&ma35d1serial_driver);
>> + if (ret)
>> + uart_unregister_driver(&ma35d1serial_reg);
>> + return ret;
>> +}
>> +
>> +static void __exit ma35d1serial_exit(void)
>> +{
>> + platform_driver_unregister(&ma35d1serial_driver);
>> + uart_unregister_driver(&ma35d1serial_reg);
>> +}
>> +
>> +module_init(ma35d1serial_init);
>> +module_exit(ma35d1serial_exit);
>> +
>> +MODULE_LICENSE("GPL v2");
> "GPL" is enough for MODULE_LICENSE, the SPDX at the start of file covers
> more specific license variations.
I got it. Thank you.
>> +MODULE_DESCRIPTION("MA35D1 serial driver");
>> +MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR);
>> +
>> diff --git a/drivers/tty/serial/ma35d1_serial.h b/drivers/tty/serial/ma35d1_serial.h
>> new file mode 100644
>> index 000000000000..5fd845c31b29
>> --- /dev/null
>> +++ b/drivers/tty/serial/ma35d1_serial.h
>> @@ -0,0 +1,93 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * MA35D1 serial driver header file
>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>> + */
>> +#ifndef __MA35D1_SERIAL_H__
>> +#define __MA35D1_SERIAL_H__
>> +
>> +/* UART Receive/Transmit Buffer Register */
>> +#define UART_REG_RBR 0x00
>> +#define UART_REG_THR 0x00
>> +
>> +/* UART Interrupt Enable Register */
>> +#define UART_REG_IER 0x04
>> +#define RDA_IEN 0x00000001 /* RBR Available Interrupt Enable */
>> +#define THRE_IEN 0x00000002 /* THR Empty Interrupt Enable */
>> +#define RLS_IEN 0x00000004 /* RX Line Status Interrupt Enable */
>> +#define RTO_IEN 0x00000010 /* RX Time-out Interrupt Enable */
>> +#define BUFERR_IEN 0x00000020 /* Buffer Error Interrupt Enable */
>> +#define TIME_OUT_EN 0x00000800 /* RX Buffer Time-out Counter Enable */
>> +
>> +/* UART FIFO Control Register */
>> +#define UART_REG_FCR 0x08
>> +#define RFR 0x00000002 /* RX Field Software Reset */
>> +#define TFR 0x00000004 /* TX Field Software Reset */
>> +
>> +/* UART Line Control Register */
>> +#define UART_REG_LCR 0x0C
>> +#define NSB 0x00000004 /* Number of “STOP Bit” */
>> +#define PBE 0x00000008 /* Parity Bit Enable */
>> +#define EPE 0x00000010 /* Even Parity Enable */
>> +#define SPE 0x00000020 /* Stick Parity Enable */
>> +#define BCB 0x00000040 /* Break Control */
>> +
>> +/* UART Modem Control Register */
>> +#define UART_REG_MCR 0x10
>> +#define RTS 0x00000020 /* nRTS Signal Control */
>> +#define RTSACTLV 0x00000200 /* nRTS Pin Active Level */
>> +#define RTSSTS 0x00002000 /* nRTS Pin Status (Read Only) */
>> +
>> +/* UART Modem Status Register */
>> +#define UART_REG_MSR 0x14
>> +#define CTSDETF 0x00000001 /* Detect nCTS State Change Flag */
>> +#define CTSSTS 0x00000010 /* nCTS Pin Status (Read Only) */
>> +#define CTSACTLV 0x00000100 /* nCTS Pin Active Level */
>> +
>> +/* UART FIFO Status Register */
>> +#define UART_REG_FSR 0x18
>> +#define RX_OVER_IF 0x00000001 /* RX Overflow Error Interrupt Flag */
>> +#define PEF 0x00000010 /* Parity Error Flag*/
>> +#define FEF 0x00000020 /* Framing Error Flag */
>> +#define BIF 0x00000040 /* Break Interrupt Flag */
>> +#define RX_EMPTY 0x00004000 /* Receiver FIFO Empty (Read Only) */
>> +#define RX_FULL 0x00008000 /* Receiver FIFO Full (Read Only) */
>> +#define TX_EMPTY 0x00400000 /* Transmitter FIFO Empty (Read Only) */
>> +#define TX_FULL 0x00800000 /* Transmitter FIFO Full (Read Only) */
>> +#define TX_OVER_IF 0x01000000 /* TX Overflow Error Interrupt Flag */
>> +#define TE_FLAG 0x10000000 /* Transmitter Empty Flag (Read Only) */
>> +
>> +/* UART Interrupt Status Register */
>> +#define UART_REG_ISR 0x1C
>> +#define RDA_IF 0x00000001 /* RBR Available Interrupt Flag */
>> +#define THRE_IF 0x00000002 /* THR Empty Interrupt Flag */
>> +#define RLSIF 0x00000004 /* Receive Line Interrupt Flag */
>> +#define MODEMIF 0x00000008 /* MODEM Interrupt Flag */
>> +#define RXTO_IF 0x00000010 /* RX Time-out Interrupt Flag */
>> +#define BUFEIF 0x00000020 /* Buffer Error Interrupt Flag */
>> +#define WK_IF 0x00000040 /* UART Wake-up Interrupt Flag */
>> +#define RDAINT 0x00000100 /* RBR Available Interrupt Indicator */
>> +#define THRE_INT 0x00000200 /* THR Empty Interrupt Indicator */
>> +
>> +/* UART Time-out Register */
>> +#define UART_REG_TOR 0x20
>> +
>> +/* UART Baud Rate Divider Register */
>> +#define UART_REG_BAUD 0x24
>> +
>> +/* UART Alternate Control/Status Register */
>> +#define UART_REG_ALT_CSR 0x2C
>> +
>> +/* UART Function Select Register */
>> +#define UART_FUN_SEL 0x30
>> +#define FUN_SEL_UART 0x00000000
>> +#define FUN_SEL_RS485 0x00000003
>> +#define FUN_SEL_MASK 0x00000007
> GENMASK(), then use FIELD_PREP() for the values.
We will fix them all.
>> +
>> +/* UART Wake-up Control Register */
>> +#define UART_REG_WKCTL 0x40
>> +
>> +/* UART Wake-up Status Register */
>> +#define UART_REG_WKSTS 0x44
>> +
>> +#endif /* __MA35D1_SERIAL_H__ */
>> diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
>> index 281fa286555c..c6d53db17042 100644
>> --- a/include/uapi/linux/serial_core.h
>> +++ b/include/uapi/linux/serial_core.h
>> @@ -279,4 +279,7 @@
>> /* Sunplus UART */
>> #define PORT_SUNPLUS 123
>>
>> +/* Nuvoton MA35D1 UART */
>> +#define PORT_MA35D1 124
>> +
>> #endif /* _UAPILINUX_SERIAL_CORE_H */
>>
Best regards,
Jacky Huang
On Mon, 20 Mar 2023, Jacky Huang wrote:
> Dear Ilpo,
>
>
> Thanks for your advice.
>
> On 2023/3/16 下午 10:54, Ilpo Järvinen wrote:
> > Hi,
> >
> > I'll not note all things below because others have already seemingly
> > commented many things.
> >
> > On Wed, 15 Mar 2023, Jacky Huang wrote:
> >
> > > From: Jacky Huang <[email protected]>
> > >
> > > This adds UART and console driver for Nuvoton ma35d1 Soc.
> > > + }
> > > + ch = (u8)serial_in(up, UART_REG_RBR);
> > Drop the case.
>
> I will fix it.
I meant "cast" in case it wasn't obvious.
> > > +/* Enable or disable the rs485 support */
> > > +static int ma35d1serial_config_rs485(struct uart_port *port,
> > > + struct ktermios *termios,
> > > + struct serial_rs485 *rs485conf)
> > > +{
> > > + struct uart_ma35d1_port *p = to_ma35d1_uart_port(port);
> > > +
> > > + p->rs485 = *rs485conf;
> > > +
> > > + if (p->rs485.delay_rts_before_send >= 1000)
> > > + p->rs485.delay_rts_before_send = 1000;
> > Don't do this in driver, the core handles the delay limits. You don't seem
> > to be using the value anyway for anything???
> >
> > Please separate the RS485 support into its own patch.
>
>
> OK, we will remove RS485 support from this initial patch.
> Once this initial patch was merged, we will submit the patch for RS485
> support.
You could do that but you could just as well include it into the same
series as another patch after the main patch.
> > > + serial_out(p, UART_FUN_SEL,
> > > + (serial_in(p, UART_FUN_SEL) & ~FUN_SEL_MASK));
> > > +
> > > + if (rs485conf->flags & SER_RS485_ENABLED) {
> > > + serial_out(p, UART_FUN_SEL,
> > > + (serial_in(p, UART_FUN_SEL) | FUN_SEL_RS485));
> > Does this pair of serial_out()s glitch the RS485 line if ->rs485_config()
> > is called while RS485 mode is already set?
> >
> > Why you need to do serial_in() from the UART_FUN_SEL twice?
>
> UART_FUN_SEL (2 bits) definition:
> 00 - UART function
> 01 - IrDA function
> 11 - RS485 function
>
> The first searial_in() is used to clear set as UART function.
> The second one is used to set RS485 function if SER_RS485_ENABLED is true.
I got that, but it doesn't answer either of my questions which are:
Can you clear the UART function without causing a glitch in the RS485?
->rs485_config() can be called while already in RS485 mode so does it
cause the UART to temporarily switch away from RS485 mode to "UART
function" until the second write.
Also, you didn't explain why you need to read the register again, does
the HW play with other bits when you do the clearing or to they remain
the same (in which case you can just use a temporary variable to store
the value)? ...It would be better to just write once too so this question
might not matter in the end.
> > > + if (pdev->dev.of_node) {
> > > + ret = of_alias_get_id(pdev->dev.of_node, "serial");
> > > + if (ret < 0) {
> > > + dev_err(&pdev->dev,
> > > + "failed to get alias/pdev id, errno %d\n",
> > > + ret);
> > Just put error prints to one line if you don't break 100 chars limit.
>
> But the checkpatch limitation is 80 characters.
No, it isn't. It was changed years ago already.
> > > +++ b/drivers/tty/serial/ma35d1_serial.h
> > > @@ -0,0 +1,93 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 */
> > > +/*
> > > + * MA35D1 serial driver header file
> > > + * Copyright (C) 2023 Nuvoton Technology Corp.
> > > + */
> > > +#ifndef __MA35D1_SERIAL_H__
> > > +#define __MA35D1_SERIAL_H__
> > > +
> > > +/* UART Receive/Transmit Buffer Register */
> > > +#define UART_REG_RBR 0x00
> > > +#define UART_REG_THR 0x00
> > > +
> > > +/* UART Interrupt Enable Register */
> > > +#define UART_REG_IER 0x04
> > > +#define RDA_IEN 0x00000001 /* RBR Available Interrupt Enable
> > > */
> > > +#define THRE_IEN 0x00000002 /* THR Empty Interrupt Enable */
> > > +#define RLS_IEN 0x00000004 /* RX Line Status Interrupt Enable
> > > */
> > > +#define RTO_IEN 0x00000010 /* RX Time-out Interrupt Enable */
> > > +#define BUFERR_IEN 0x00000020 /* Buffer Error Interrupt Enable */
> > > +#define TIME_OUT_EN 0x00000800 /* RX Buffer Time-out Counter
> > > Enable */
> > > +
> > > +/* UART FIFO Control Register */
> > > +#define UART_REG_FCR 0x08
> > > +#define RFR 0x00000002 /* RX Field Software Reset */
> > > +#define TFR 0x00000004 /* TX Field Software Reset */
> > > +
> > > +/* UART Line Control Register */
> > > +#define UART_REG_LCR 0x0C
> > > +#define NSB 0x00000004 /* Number of “STOP Bit” */
> > > +#define PBE 0x00000008 /* Parity Bit Enable */
> > > +#define EPE 0x00000010 /* Even Parity Enable */
> > > +#define SPE 0x00000020 /* Stick Parity Enable */
> > > +#define BCB 0x00000040 /* Break Control */
> > > +
> > > +/* UART Modem Control Register */
> > > +#define UART_REG_MCR 0x10
> > > +#define RTS 0x00000020 /* nRTS Signal Control */
> > > +#define RTSACTLV 0x00000200 /* nRTS Pin Active Level */
> > > +#define RTSSTS 0x00002000 /* nRTS Pin Status (Read Only) */
> > > +
> > > +/* UART Modem Status Register */
> > > +#define UART_REG_MSR 0x14
> > > +#define CTSDETF 0x00000001 /* Detect nCTS State Change Flag */
> > > +#define CTSSTS 0x00000010 /* nCTS Pin Status (Read Only) */
> > > +#define CTSACTLV 0x00000100 /* nCTS Pin Active Level */
> > > +
> > > +/* UART FIFO Status Register */
> > > +#define UART_REG_FSR 0x18
> > > +#define RX_OVER_IF 0x00000001 /* RX Overflow Error Interrupt Flag */
> > > +#define PEF 0x00000010 /* Parity Error Flag*/
> > > +#define FEF 0x00000020 /* Framing Error Flag */
> > > +#define BIF 0x00000040 /* Break Interrupt Flag */
> > > +#define RX_EMPTY 0x00004000 /* Receiver FIFO Empty (Read Only) */
> > > +#define RX_FULL 0x00008000 /* Receiver FIFO Full (Read Only)
> > > */
> > > +#define TX_EMPTY 0x00400000 /* Transmitter FIFO Empty (Read Only) */
> > > +#define TX_FULL 0x00800000 /* Transmitter FIFO Full (Read
> > > Only) */
> > > +#define TX_OVER_IF 0x01000000 /* TX Overflow Error Interrupt Flag */
> > > +#define TE_FLAG 0x10000000 /* Transmitter Empty Flag (Read
> > > Only) */
> > > +
> > > +/* UART Interrupt Status Register */
> > > +#define UART_REG_ISR 0x1C
> > > +#define RDA_IF 0x00000001 /* RBR Available Interrupt Flag */
> > > +#define THRE_IF 0x00000002 /* THR Empty Interrupt Flag */
> > > +#define RLSIF 0x00000004 /* Receive Line Interrupt Flag */
> > > +#define MODEMIF 0x00000008 /* MODEM Interrupt Flag */
> > > +#define RXTO_IF 0x00000010 /* RX Time-out Interrupt Flag */
> > > +#define BUFEIF 0x00000020 /* Buffer Error Interrupt Flag */
> > > +#define WK_IF 0x00000040 /* UART Wake-up Interrupt Flag */
> > > +#define RDAINT 0x00000100 /* RBR Available Interrupt
> > > Indicator */
> > > +#define THRE_INT 0x00000200 /* THR Empty Interrupt Indicator */
I forgot to mention earlier, there are many defines above which should use
BIT().
--
i.
On Sun, 19 Mar 2023, Jacky Huang wrote:
>
> On 2023/3/16 下午 11:56, Ilpo Järvinen wrote:
> > On Wed, 15 Mar 2023, Jacky Huang wrote:
> >
> > > From: Jacky Huang <[email protected]>
> > >
> > > The clock controller generates clocks for the whole chip, including
> > > system clocks and all peripheral clocks. This driver support ma35d1
> > > clock gating, divider, and individual PLL configuration.
> > >
> > > There are 6 PLLs in ma35d1 SoC:
> > > - CA-PLL for the two Cortex-A35 CPU clock
> > > - SYS-PLL for system bus, which comes from the companion MCU
> > > and cannot be programmed by clock controller.
> > > - DDR-PLL for DDR
> > > - EPLL for GMAC and GFX, Display, and VDEC IPs.
> > > - VPLL for video output pixel clock
> > > - APLL for SDHC, I2S audio, and other IPs.
> > > CA-PLL has only one operation mode.
> > > DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
> > > operation modes: integer mode, fraction mode, and spread specturm mode.
> > >
> > > Signed-off-by: Jacky Huang <[email protected]>
> > > ---
> > > +};
> > > +
> > > +#define to_ma35d1_adc_clk_divider(_hw) \
> > > + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
> > static inline
>
>
> I will modify these "static" functions as "static inline".
No, that's not what I meant. Make the container_of define static inline
function instead, no other functions. (Or if you have more than one of
such, all of them of course).
> > > +}
> > > diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c
> > > b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> > > new file mode 100644
> > > index 000000000000..79e724b148fa
> > > --- /dev/null
> > > +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
> > > @@ -0,0 +1,534 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Copyright (C) 2023 Nuvoton Technology Corp.
> > > + * Author: Chi-Fang Li <[email protected]>
> > > + */
> > > +
> > > +#include <linux/clk.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/io.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/bitfield.h>
> > > +
> > > +#include "clk-ma35d1.h"
> > > +
> > > +#define to_ma35d1_clk_pll(clk) \
> > > + (container_of(clk, struct ma35d1_clk_pll, clk))
> > static inline
>
>
> I am sorry cannot get "static inline" refer to which one.
>
> Would you give more advice here?
>
> Thank you.
static inline struct ...type_here... *to_ma35d1_clk_pll(struct ...type_here... *clk)
{
return container_of(clk, struct ma35d1_clk_pll, clk);
}
> > > + } else {
> > > + pr_err("Failed to set rate %ld\n", u64PllFreq);
> > > + return 0;
> > > + }
> > > +
> > > + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
> > > + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
> > > + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
> > Ditto.
> >
> > Is here some ...ROUND_UP() trick hidden too?
>
>
> This follows the description of PLL spec.
Right but I was looking into what the math does. To me this looks like
rounding up:
VSIPLL_FCLK_MIN_FREQ / u64FCLKO + (VSIPLL_FCLK_MIN_FREQ % u64FCLKO ? 1 : 0)
When modulo is > 0, add one, which is round up, no?
There are helpers which you should use for rounding up, search for
*_ROUND_UP. I think math64.h had one 64-bit one.
> > > + u64X = u64tmp % 1000;
> > > + u32FRAC = ((u64X << 24) + 500) / 1000;
I missed this earlier, is this rounding? ...Use a helper if it is.
Otherwise define what 500 is. (No need to answer despite question mark,
just do the change).
> > > +
> > > + u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
> > > + u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
> > > +
> > > + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
> > Is some *SEC_PER_*SEC define relevant for 1000 ?
> >
> > Or some other units, e.g., HZ related?
>
>
> 1000 is for kHz to MHz, and 100 is for percentage.
Okay, then use KHZ_PER_MHZ from linux/units.h.
We don't have anything for percents under include/ I think so that can be
left as literal.
> > > + switch (pll->mode) {
> > > + case VSIPLL_INTEGER_MODE:
> > > + u64PllClk = CLK_CalPLLFreq_Mode0(PllSrcClk, u64PllFreq,
> > > + u32Reg);
> > One line.
>
>
> It will exceed 80 characters in one line.
Yeah, the semicolon won't fit to 80 chars :-) which means there won't be
significant information loss even on 80 chars terminal. This kind of cases
is why checkpatch won't complain until 100 chars. Use common sense (don't
hide most of the logic to 80-100 but don't be afraid of breaking the 80
chars where the information loss is not significant issue).
Besides, once you removed the types from variable names, it will be
shorter anyway.
--
i.
Dear Arnd,
On 2023/3/18 下午 10:04, Arnd Bergmann wrote:
> On Sat, Mar 18, 2023, at 14:17, Jacky Huang wrote:
>> On 2023/3/16 下午 10:17, Arnd Bergmann wrote:
>>> On Wed, Mar 15, 2023, at 08:28, Jacky Huang wrote:
>>>> + mem: memory@80000000 {
>>>> + device_type = "memory";
>>>> + reg = <0x00000000 0x80000000 0 0x20000000>; /* 512M DRAM */
>>>> + };
>>>> +};
>>> In most machines, the memory size is detected by the boot loader
>>> and filled in the dtb in memory before starting the kernel, so
>>> you should not need two separate files here for the two common
>>> memory configurations.
>>
>> On ma35d1, memory size is determined early before uboot.
>>
>> BL1 (MaskROM boot code) -> BL2 (arm-trust-firmware) -> BL32 (op-tee) &
>> BL33 (uboot).
>> The DDR was initialized in BL2 stage with a selected DDR setting, which
>> is hard coded, including DDR size.
>>
>> We searched the arm64 dts and found that almost all vendors claimed
>> memory size in board level dtsi/dts. This seems to be common.
>>
>> So, can we have it unchanged?
> I see the memory size encoded in about one out of three .dts files,
> which is more than I expected. It's clearly not harmful to have it
> listed in the dts, it just shouldn't be necessary.
>
> If it helps you with your current u-boot, then leave it in, but
> consider adding detection logic into u-boot so it can override
> the value in the dtb file at boot time.
Thank you for your understanding. As more drivers are added, I think
this memory
size encoded will look less conspicuous. In fact, in the previous arm9
project, we
did detect the memory size by uboot, and then passed it to the kernel.
If there is
a need in the future, we will consider to support it in ma35d1.
>>> Since the machine is called 'som', I would assume that this is a
>>> module that is integrated on another board, so more commonly one
>>> would have a dtsi file for the som in addition to the one for the
>>> soc, and have all the components of the module listed in this
>>> file, while the dts file that includes the som.dtsi lists the
>>> devices on the carrier board and enables the on-chip devices
>>> that are connected to the outside.
>>>
>> You are right, ma35d1 som have a base board, and a cpu board on it.
>>
>> It is a good suggestion that we should have a dtsi for som base board.
>>
>> Consider that we are in the initial submit, and such a dtsi will be an empty
>> file at this stage. So, I would like to do it when peripheral drivers
>> upstream started. Is it ok?
> It's not a big deal either way. I if you want to keep it only with
> one dts file and one dtsi file, that's fine, but maybe rename the dts
> file based on the name of the carrier rather than the SoM in this
> case.
>
> Arnd
Thank you. As the dts names are consistent with the ma35d1 BSP on
linux-5.10.y,
we would like to keep the consistence still.
Best regards,
Jacky Huang
Dear Ilpo,
On 2023/3/20 下午 06:04, Ilpo Järvinen wrote:
> On Mon, 20 Mar 2023, Jacky Huang wrote:
>
>> Dear Ilpo,
>>
>>
>> Thanks for your advice.
>>
>> On 2023/3/16 下午 10:54, Ilpo Järvinen wrote:
>>> Hi,
>>>
>>> I'll not note all things below because others have already seemingly
>>> commented many things.
>>>
>>> On Wed, 15 Mar 2023, Jacky Huang wrote:
>>>
>>>> From: Jacky Huang <[email protected]>
>>>>
>>>> This adds UART and console driver for Nuvoton ma35d1 Soc.
>>>> + }
>>>> + ch = (u8)serial_in(up, UART_REG_RBR);
>>> Drop the case.
>> I will fix it.
> I meant "cast" in case it wasn't obvious.
I know that, thank you.
>>>> +/* Enable or disable the rs485 support */
>>>> +static int ma35d1serial_config_rs485(struct uart_port *port,
>>>> + struct ktermios *termios,
>>>> + struct serial_rs485 *rs485conf)
>>>> +{
>>>> + struct uart_ma35d1_port *p = to_ma35d1_uart_port(port);
>>>> +
>>>> + p->rs485 = *rs485conf;
>>>> +
>>>> + if (p->rs485.delay_rts_before_send >= 1000)
>>>> + p->rs485.delay_rts_before_send = 1000;
>>> Don't do this in driver, the core handles the delay limits. You don't seem
>>> to be using the value anyway for anything???
>>>
>>> Please separate the RS485 support into its own patch.
>>
>> OK, we will remove RS485 support from this initial patch.
>> Once this initial patch was merged, we will submit the patch for RS485
>> support.
> You could do that but you could just as well include it into the same
> series as another patch after the main patch.
>>>> + serial_out(p, UART_FUN_SEL,
>>>> + (serial_in(p, UART_FUN_SEL) & ~FUN_SEL_MASK));
>>>> +
>>>> + if (rs485conf->flags & SER_RS485_ENABLED) {
>>>> + serial_out(p, UART_FUN_SEL,
>>>> + (serial_in(p, UART_FUN_SEL) | FUN_SEL_RS485));
>>> Does this pair of serial_out()s glitch the RS485 line if ->rs485_config()
>>> is called while RS485 mode is already set?
>>>
>>> Why you need to do serial_in() from the UART_FUN_SEL twice?
>> UART_FUN_SEL (2 bits) definition:
>> 00 - UART function
>> 01 - IrDA function
>> 11 - RS485 function
>>
>> The first searial_in() is used to clear set as UART function.
>> The second one is used to set RS485 function if SER_RS485_ENABLED is true.
> I got that, but it doesn't answer either of my questions which are:
>
> Can you clear the UART function without causing a glitch in the RS485?
> ->rs485_config() can be called while already in RS485 mode so does it
> cause the UART to temporarily switch away from RS485 mode to "UART
> function" until the second write.
>
> Also, you didn't explain why you need to read the register again, does
> the HW play with other bits when you do the clearing or to they remain
> the same (in which case you can just use a temporary variable to store
> the value)? ...It would be better to just write once too so this question
> might not matter in the end.
Thank you for the detailed explanation.
OK, the register won't change. I will modify the code to read once and
write once only.
>>>> + if (pdev->dev.of_node) {
>>>> + ret = of_alias_get_id(pdev->dev.of_node, "serial");
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev,
>>>> + "failed to get alias/pdev id, errno %d\n",
>>>> + ret);
>>> Just put error prints to one line if you don't break 100 chars limit.
>> But the checkpatch limitation is 80 characters.
> No, it isn't. It was changed years ago already.
I have a test on the checkpatch script.
You are right. It won't complain about over 80 characters now.
>>>> +++ b/drivers/tty/serial/ma35d1_serial.h
>>>> @@ -0,0 +1,93 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>>> +/*
>>>> + * MA35D1 serial driver header file
>>>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>>>> + */
>>>> +#ifndef __MA35D1_SERIAL_H__
>>>> +#define __MA35D1_SERIAL_H__
>>>> +
>>>> +/* UART Receive/Transmit Buffer Register */
>>>> +#define UART_REG_RBR 0x00
>>>> +#define UART_REG_THR 0x00
>>>> +
>>>> +/* UART Interrupt Enable Register */
>>>> +#define UART_REG_IER 0x04
>>>> +#define RDA_IEN 0x00000001 /* RBR Available Interrupt Enable
>>>> */
>>>> +#define THRE_IEN 0x00000002 /* THR Empty Interrupt Enable */
>>>> +#define RLS_IEN 0x00000004 /* RX Line Status Interrupt Enable
>>>> */
>>>> +#define RTO_IEN 0x00000010 /* RX Time-out Interrupt Enable */
>>>> +#define BUFERR_IEN 0x00000020 /* Buffer Error Interrupt Enable */
>>>> +#define TIME_OUT_EN 0x00000800 /* RX Buffer Time-out Counter
>>>> Enable */
>>>> +
>>>> +/* UART FIFO Control Register */
>>>> +#define UART_REG_FCR 0x08
>>>> +#define RFR 0x00000002 /* RX Field Software Reset */
>>>> +#define TFR 0x00000004 /* TX Field Software Reset */
>>>> +
>>>> +/* UART Line Control Register */
>>>> +#define UART_REG_LCR 0x0C
>>>> +#define NSB 0x00000004 /* Number of “STOP Bit” */
>>>> +#define PBE 0x00000008 /* Parity Bit Enable */
>>>> +#define EPE 0x00000010 /* Even Parity Enable */
>>>> +#define SPE 0x00000020 /* Stick Parity Enable */
>>>> +#define BCB 0x00000040 /* Break Control */
>>>> +
>>>> +/* UART Modem Control Register */
>>>> +#define UART_REG_MCR 0x10
>>>> +#define RTS 0x00000020 /* nRTS Signal Control */
>>>> +#define RTSACTLV 0x00000200 /* nRTS Pin Active Level */
>>>> +#define RTSSTS 0x00002000 /* nRTS Pin Status (Read Only) */
>>>> +
>>>> +/* UART Modem Status Register */
>>>> +#define UART_REG_MSR 0x14
>>>> +#define CTSDETF 0x00000001 /* Detect nCTS State Change Flag */
>>>> +#define CTSSTS 0x00000010 /* nCTS Pin Status (Read Only) */
>>>> +#define CTSACTLV 0x00000100 /* nCTS Pin Active Level */
>>>> +
>>>> +/* UART FIFO Status Register */
>>>> +#define UART_REG_FSR 0x18
>>>> +#define RX_OVER_IF 0x00000001 /* RX Overflow Error Interrupt Flag */
>>>> +#define PEF 0x00000010 /* Parity Error Flag*/
>>>> +#define FEF 0x00000020 /* Framing Error Flag */
>>>> +#define BIF 0x00000040 /* Break Interrupt Flag */
>>>> +#define RX_EMPTY 0x00004000 /* Receiver FIFO Empty (Read Only) */
>>>> +#define RX_FULL 0x00008000 /* Receiver FIFO Full (Read Only)
>>>> */
>>>> +#define TX_EMPTY 0x00400000 /* Transmitter FIFO Empty (Read Only) */
>>>> +#define TX_FULL 0x00800000 /* Transmitter FIFO Full (Read
>>>> Only) */
>>>> +#define TX_OVER_IF 0x01000000 /* TX Overflow Error Interrupt Flag */
>>>> +#define TE_FLAG 0x10000000 /* Transmitter Empty Flag (Read
>>>> Only) */
>>>> +
>>>> +/* UART Interrupt Status Register */
>>>> +#define UART_REG_ISR 0x1C
>>>> +#define RDA_IF 0x00000001 /* RBR Available Interrupt Flag */
>>>> +#define THRE_IF 0x00000002 /* THR Empty Interrupt Flag */
>>>> +#define RLSIF 0x00000004 /* Receive Line Interrupt Flag */
>>>> +#define MODEMIF 0x00000008 /* MODEM Interrupt Flag */
>>>> +#define RXTO_IF 0x00000010 /* RX Time-out Interrupt Flag */
>>>> +#define BUFEIF 0x00000020 /* Buffer Error Interrupt Flag */
>>>> +#define WK_IF 0x00000040 /* UART Wake-up Interrupt Flag */
>>>> +#define RDAINT 0x00000100 /* RBR Available Interrupt
>>>> Indicator */
>>>> +#define THRE_INT 0x00000200 /* THR Empty Interrupt Indicator */
> I forgot to mention earlier, there are many defines above which should use
> BIT().
>
Sure we will fix them all.
Best regards,
Jacky Huang
Dear Ilpo,
On 2023/3/20 下午 06:31, Ilpo Järvinen wrote:
> On Sun, 19 Mar 2023, Jacky Huang wrote:
>
>> On 2023/3/16 下午 11:56, Ilpo Järvinen wrote:
>>> On Wed, 15 Mar 2023, Jacky Huang wrote:
>>>
>>>> From: Jacky Huang <[email protected]>
>>>>
>>>> The clock controller generates clocks for the whole chip, including
>>>> system clocks and all peripheral clocks. This driver support ma35d1
>>>> clock gating, divider, and individual PLL configuration.
>>>>
>>>> There are 6 PLLs in ma35d1 SoC:
>>>> - CA-PLL for the two Cortex-A35 CPU clock
>>>> - SYS-PLL for system bus, which comes from the companion MCU
>>>> and cannot be programmed by clock controller.
>>>> - DDR-PLL for DDR
>>>> - EPLL for GMAC and GFX, Display, and VDEC IPs.
>>>> - VPLL for video output pixel clock
>>>> - APLL for SDHC, I2S audio, and other IPs.
>>>> CA-PLL has only one operation mode.
>>>> DDR-PLL, EPLL, VPLL, and APLL are advanced PLLs which have 3
>>>> operation modes: integer mode, fraction mode, and spread specturm mode.
>>>>
>>>> Signed-off-by: Jacky Huang <[email protected]>
>>>> ---
>>>> +};
>>>> +
>>>> +#define to_ma35d1_adc_clk_divider(_hw) \
>>>> + container_of(_hw, struct ma35d1_adc_clk_divider, hw)
>>> static inline
>>
>> I will modify these "static" functions as "static inline".
> No, that's not what I meant. Make the container_of define static inline
> function instead, no other functions. (Or if you have more than one of
> such, all of them of course).
>
>>>> +}
>>>> diff --git a/drivers/clk/nuvoton/clk-ma35d1-pll.c
>>>> b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>>>> new file mode 100644
>>>> index 000000000000..79e724b148fa
>>>> --- /dev/null
>>>> +++ b/drivers/clk/nuvoton/clk-ma35d1-pll.c
>>>> @@ -0,0 +1,534 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +/*
>>>> + * Copyright (C) 2023 Nuvoton Technology Corp.
>>>> + * Author: Chi-Fang Li <[email protected]>
>>>> + */
>>>> +
>>>> +#include <linux/clk.h>
>>>> +#include <linux/clk-provider.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/bitfield.h>
>>>> +
>>>> +#include "clk-ma35d1.h"
>>>> +
>>>> +#define to_ma35d1_clk_pll(clk) \
>>>> + (container_of(clk, struct ma35d1_clk_pll, clk))
>>> static inline
>>
>> I am sorry cannot get "static inline" refer to which one.
>>
>> Would you give more advice here?
>>
>> Thank you.
> static inline struct ...type_here... *to_ma35d1_clk_pll(struct ...type_here... *clk)
> {
> return container_of(clk, struct ma35d1_clk_pll, clk);
> }
>
OK, I got it. Thank you very much.
>>>> + } else {
>>>> + pr_err("Failed to set rate %ld\n", u64PllFreq);
>>>> + return 0;
>>>> + }
>>>> +
>>>> + u64P = (u64FCLKO >= VSIPLL_FCLK_MIN_FREQ) ? 1 :
>>>> + ((VSIPLL_FCLK_MIN_FREQ / u64FCLKO) +
>>>> + ((VSIPLL_FCLK_MIN_FREQ % u64FCLKO) ? 1 : 0));
>>> Ditto.
>>>
>>> Is here some ...ROUND_UP() trick hidden too?
>>
>> This follows the description of PLL spec.
> Right but I was looking into what the math does. To me this looks like
> rounding up:
> VSIPLL_FCLK_MIN_FREQ / u64FCLKO + (VSIPLL_FCLK_MIN_FREQ % u64FCLKO ? 1 : 0)
>
> When modulo is > 0, add one, which is round up, no?
>
> There are helpers which you should use for rounding up, search for
> *_ROUND_UP. I think math64.h had one 64-bit one.
Yes, it is a round up. We will find out all the occurrence and use
ROUND_UP() macro instead.
>>>> + u64X = u64tmp % 1000;
>>>> + u32FRAC = ((u64X << 24) + 500) / 1000;
> I missed this earlier, is this rounding? ...Use a helper if it is.
> Otherwise define what 500 is. (No need to answer despite question mark,
> just do the change).
>
>>>> +
>>>> + u64SSRATE = ((PllSrcClk >> 1) / (u32Fmod * 2)) - 1;
>>>> + u64SLOPE = ((u64tmp * u32SR / u64SSRATE) << 24) / 100 / 1000;
>>>> +
>>>> + u64PllClk = (PllSrcClk * u64tmp) / u64P / u64M / 1000;
>>> Is some *SEC_PER_*SEC define relevant for 1000 ?
>>>
>>> Or some other units, e.g., HZ related?
>>
>> 1000 is for kHz to MHz, and 100 is for percentage.
> Okay, then use KHZ_PER_MHZ from linux/units.h.
>
> We don't have anything for percents under include/ I think so that can be
> left as literal.
Sure, we are rewriting the pll calculation routine and add formula
comments to make it more readable.
>>>> + switch (pll->mode) {
>>>> + case VSIPLL_INTEGER_MODE:
>>>> + u64PllClk = CLK_CalPLLFreq_Mode0(PllSrcClk, u64PllFreq,
>>>> + u32Reg);
>>> One line.
>>
>> It will exceed 80 characters in one line.
> Yeah, the semicolon won't fit to 80 chars :-) which means there won't be
> significant information loss even on 80 chars terminal. This kind of cases
> is why checkpatch won't complain until 100 chars. Use common sense (don't
> hide most of the logic to 80-100 but don't be afraid of breaking the 80
> chars where the information loss is not significant issue).
>
> Besides, once you removed the types from variable names, it will be
> shorter anyway.
>
Got it. Thanks for your kind help and detailed explanation.
Best regards,
Jacky Huang