Re: [PATCH 04/12] tty: serial: samsung: Init USI to keep clocks running

From: Krzysztof Kozlowski
Date: Fri Jul 30 2021 - 12:32:24 EST


On 30/07/2021 16:49, Sam Protsenko wrote:
> UART block is a part of USI (Universal Serial Interface) IP-core in
> Samsung SoCs since Exynos9810 (e.g. in Exynos850). USI allows one to
> enable one of three types of serial interface: UART, SPI or I2C. That's
> possible because USI shares almost all internal circuits within each
> protocol. USI also provides some additional registers so it's possible
> to configure it.
>
> One USI register called USI_OPTION has reset value of 0x0. Because of
> this the clock gating behavior is controlled by hardware (HWACG =
> Hardware Auto Clock Gating), which simply means the serial won't work
> after reset as is. In order to make it work, USI_OPTION[2:1] bits must
> be set to 0b01, so that HWACG is controlled manually (by software).
> Bits meaning:
> - CLKREQ_ON = 1: clock is continuously provided to IP
> - CLKSTOP_ON = 0: drive IP_CLKREQ to High (needs to be set along with
> CLKREQ_ON = 1)
>
> USI is not present on older chips, like s3c2410, s3c2412, s3c2440,
> s3c6400, s5pv210, exynos5433, exynos4210. So the new boolean field
> '.has_usi' was added to struct s3c24xx_uart_info. USI registers will be
> only actually accessed when '.has_usi' field is set to "1".
>
> This feature is needed for further serial enablement on Exynos850, but
> some other new Exynos chips (like Exynos9810) may benefit from this
> feature as well.
>
> Signed-off-by: Sam Protsenko <semen.protsenko@xxxxxxxxxx>
> ---
> drivers/tty/serial/samsung_tty.c | 33 +++++++++++++++++++++++++++++++-
> include/linux/serial_s3c.h | 9 +++++++++
> 2 files changed, 41 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
> index 9fbc61151c2e..0f3cbd0b37e3 100644
> --- a/drivers/tty/serial/samsung_tty.c
> +++ b/drivers/tty/serial/samsung_tty.c
> @@ -65,6 +65,7 @@ enum s3c24xx_port_type {
> struct s3c24xx_uart_info {
> char *name;
> enum s3c24xx_port_type type;
> + unsigned int has_usi;
> unsigned int port_type;
> unsigned int fifosize;
> unsigned long rx_fifomask;
> @@ -1352,6 +1353,29 @@ static int apple_s5l_serial_startup(struct uart_port *port)
> return ret;
> }
>
> +static void exynos_usi_init(struct uart_port *port)
> +{
> + struct s3c24xx_uart_port *ourport = to_ourport(port);
> + struct s3c24xx_uart_info *info = ourport->info;
> +
> + if (!info->has_usi)
> + return;
> +
> + /*
> + * USI_RESET is an active High signal. Reset value of USI_RESET is 0x1
> + * to drive stable value to PAD. Due to this feature, the USI_RESET must
> + * be cleared (set as 0x0) before starting a transaction.

"before starting a transaction" suggests it is related with transaction
or something before starting it. Don't you need it simply after reset or
resume?

> + */
> + wr_regl(port, USI_CON, USI_RESET);

You are clearing entire register, not only USI_RESET bitfield. Is it
really what you want?

> + udelay(1);
> +
> + /*
> + * Set the HWACG option bit in case of UART Rx mode.
> + * CLKREQ_ON = 1, CLKSTOP_ON = 0 (set USI_OPTION[2:1] = 0x1).
> + */
> + wr_regl(port, USI_OPTION, USI_HWACG_CLKREQ_ON);
> +}
> +
> /* power power management control */
>
> static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
> @@ -1379,6 +1403,7 @@ static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
> if (!IS_ERR(ourport->baudclk))
> clk_prepare_enable(ourport->baudclk);
>
> + exynos_usi_init(port);
> break;
> default:
> dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level);
> @@ -2102,6 +2127,8 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
> if (ret)
> pr_warn("uart: failed to enable baudclk\n");
>
> + exynos_usi_init(port);
> +
> /* Keep all interrupts masked and cleared */
> switch (ourport->info->type) {
> case TYPE_S3C6400:
> @@ -2750,10 +2777,11 @@ static struct s3c24xx_serial_drv_data s5pv210_serial_drv_data = {
> #endif
>
> #if defined(CONFIG_ARCH_EXYNOS)
> -#define EXYNOS_COMMON_SERIAL_DRV_DATA \
> +#define EXYNOS_COMMON_SERIAL_DRV_DATA_USI(_has_usi) \
> .info = &(struct s3c24xx_uart_info) { \
> .name = "Samsung Exynos UART", \
> .type = TYPE_S3C6400, \
> + .has_usi = _has_usi, \
> .port_type = PORT_S3C6400, \
> .has_divslot = 1, \
> .rx_fifomask = S5PV210_UFSTAT_RXMASK, \
> @@ -2773,6 +2801,9 @@ static struct s3c24xx_serial_drv_data s5pv210_serial_drv_data = {
> .has_fracval = 1, \
> } \
>
> +#define EXYNOS_COMMON_SERIAL_DRV_DATA \
> + EXYNOS_COMMON_SERIAL_DRV_DATA_USI(0)
> +
> static struct s3c24xx_serial_drv_data exynos4210_serial_drv_data = {
> EXYNOS_COMMON_SERIAL_DRV_DATA,
> .fifosize = { 256, 64, 16, 16 },
> diff --git a/include/linux/serial_s3c.h b/include/linux/serial_s3c.h
> index f6c3323fc4c5..013c2646863e 100644
> --- a/include/linux/serial_s3c.h
> +++ b/include/linux/serial_s3c.h
> @@ -28,6 +28,15 @@
> #define S3C2410_UFSTAT (0x18)
> #define S3C2410_UMSTAT (0x1C)
>
> +/* USI Control Register offset */
> +#define USI_CON (0xC4)
> +/* USI Option Register offset */
> +#define USI_OPTION (0xC8)
> +/* USI_CON[0] = 0b0: clear USI global software reset (Active High) */
> +#define USI_RESET (0<<0)

Just 0x0. I understand you wanted to hint it is a bit field, but the
shift of 0 actually creates more questions.


Best regards,
Krzysztof