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