@@ -330,8 +330,9 @@ struct sc16is7xx_one {
330330 struct kthread_work reg_work ;
331331 struct kthread_delayed_work ms_work ;
332332 struct sc16is7xx_one_config config ;
333- bool irda_mode ;
334333 unsigned int old_mctrl ;
334+ u8 old_lcr ; /* Value before EFR access. */
335+ bool irda_mode ;
335336};
336337
337338struct sc16is7xx_port {
@@ -412,6 +413,49 @@ static void sc16is7xx_power(struct uart_port *port, int on)
412413 on ? 0 : SC16IS7XX_IER_SLEEP_BIT );
413414}
414415
416+ /*
417+ * In an amazing feat of design, the Enhanced Features Register (EFR)
418+ * shares the address of the Interrupt Identification Register (IIR).
419+ * Access to EFR is switched on by writing a magic value (0xbf) to the
420+ * Line Control Register (LCR). Any interrupt firing during this time will
421+ * see the EFR where it expects the IIR to be, leading to
422+ * "Unexpected interrupt" messages.
423+ *
424+ * Prevent this possibility by claiming a mutex while accessing the EFR,
425+ * and claiming the same mutex from within the interrupt handler. This is
426+ * similar to disabling the interrupt, but that doesn't work because the
427+ * bulk of the interrupt processing is run as a workqueue job in thread
428+ * context.
429+ */
430+ static void sc16is7xx_efr_lock (struct uart_port * port )
431+ {
432+ struct sc16is7xx_one * one = to_sc16is7xx_one (port , port );
433+
434+ mutex_lock (& one -> efr_lock );
435+
436+ /* Backup content of LCR. */
437+ one -> old_lcr = sc16is7xx_port_read (port , SC16IS7XX_LCR_REG );
438+
439+ /* Enable access to Enhanced register set */
440+ sc16is7xx_port_write (port , SC16IS7XX_LCR_REG , SC16IS7XX_LCR_CONF_MODE_B );
441+
442+ /* Disable cache updates when writing to EFR registers */
443+ regcache_cache_bypass (one -> regmap , true);
444+ }
445+
446+ static void sc16is7xx_efr_unlock (struct uart_port * port )
447+ {
448+ struct sc16is7xx_one * one = to_sc16is7xx_one (port , port );
449+
450+ /* Re-enable cache updates when writing to normal registers */
451+ regcache_cache_bypass (one -> regmap , false);
452+
453+ /* Restore original content of LCR */
454+ sc16is7xx_port_write (port , SC16IS7XX_LCR_REG , one -> old_lcr );
455+
456+ mutex_unlock (& one -> efr_lock );
457+ }
458+
415459static void sc16is7xx_ier_clear (struct uart_port * port , u8 bit )
416460{
417461 struct sc16is7xx_port * s = dev_get_drvdata (port -> dev );
@@ -522,45 +566,19 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
522566 div /= 4 ;
523567 }
524568
525- /* In an amazing feat of design, the Enhanced Features Register shares
526- * the address of the Interrupt Identification Register, and is
527- * switched in by writing a magic value (0xbf) to the Line Control
528- * Register. Any interrupt firing during this time will see the EFR
529- * where it expects the IIR to be, leading to "Unexpected interrupt"
530- * messages.
531- *
532- * Prevent this possibility by claiming a mutex while accessing the
533- * EFR, and claiming the same mutex from within the interrupt handler.
534- * This is similar to disabling the interrupt, but that doesn't work
535- * because the bulk of the interrupt processing is run as a workqueue
536- * job in thread context.
537- */
538- mutex_lock (& one -> efr_lock );
539-
540- lcr = sc16is7xx_port_read (port , SC16IS7XX_LCR_REG );
541-
542- /* Open the LCR divisors for configuration */
543- sc16is7xx_port_write (port , SC16IS7XX_LCR_REG ,
544- SC16IS7XX_LCR_CONF_MODE_B );
545-
546569 /* Enable enhanced features */
547- regcache_cache_bypass ( one -> regmap , true );
570+ sc16is7xx_efr_lock ( port );
548571 sc16is7xx_port_update (port , SC16IS7XX_EFR_REG ,
549572 SC16IS7XX_EFR_ENABLE_BIT ,
550573 SC16IS7XX_EFR_ENABLE_BIT );
551-
552- regcache_cache_bypass (one -> regmap , false);
553-
554- /* Put LCR back to the normal mode */
555- sc16is7xx_port_write (port , SC16IS7XX_LCR_REG , lcr );
556-
557- mutex_unlock (& one -> efr_lock );
574+ sc16is7xx_efr_unlock (port );
558575
559576 sc16is7xx_port_update (port , SC16IS7XX_MCR_REG ,
560577 SC16IS7XX_MCR_CLKSEL_BIT ,
561578 prescaler );
562579
563- /* Open the LCR divisors for configuration */
580+ /* Backup LCR and access special register set (DLL/DLH) */
581+ lcr = sc16is7xx_port_read (port , SC16IS7XX_LCR_REG );
564582 sc16is7xx_port_write (port , SC16IS7XX_LCR_REG ,
565583 SC16IS7XX_LCR_CONF_MODE_A );
566584
@@ -570,7 +588,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
570588 sc16is7xx_port_write (port , SC16IS7XX_DLL_REG , div % 256 );
571589 regcache_cache_bypass (one -> regmap , false);
572590
573- /* Put LCR back to the normal mode */
591+ /* Restore LCR and access to general register set */
574592 sc16is7xx_port_write (port , SC16IS7XX_LCR_REG , lcr );
575593
576594 return DIV_ROUND_CLOSEST (clk / 16 , div );
@@ -1049,17 +1067,7 @@ static void sc16is7xx_set_termios(struct uart_port *port,
10491067 if (!(termios -> c_cflag & CREAD ))
10501068 port -> ignore_status_mask |= SC16IS7XX_LSR_BRK_ERROR_MASK ;
10511069
1052- /* As above, claim the mutex while accessing the EFR. */
1053- mutex_lock (& one -> efr_lock );
1054-
1055- sc16is7xx_port_write (port , SC16IS7XX_LCR_REG ,
1056- SC16IS7XX_LCR_CONF_MODE_B );
1057-
10581070 /* Configure flow control */
1059- regcache_cache_bypass (one -> regmap , true);
1060- sc16is7xx_port_write (port , SC16IS7XX_XON1_REG , termios -> c_cc [VSTART ]);
1061- sc16is7xx_port_write (port , SC16IS7XX_XOFF1_REG , termios -> c_cc [VSTOP ]);
1062-
10631071 port -> status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS );
10641072 if (termios -> c_cflag & CRTSCTS ) {
10651073 flow |= SC16IS7XX_EFR_AUTOCTS_BIT |
@@ -1071,16 +1079,16 @@ static void sc16is7xx_set_termios(struct uart_port *port,
10711079 if (termios -> c_iflag & IXOFF )
10721080 flow |= SC16IS7XX_EFR_SWFLOW1_BIT ;
10731081
1074- sc16is7xx_port_update (port ,
1075- SC16IS7XX_EFR_REG ,
1076- SC16IS7XX_EFR_FLOWCTRL_BITS ,
1077- flow );
1078- regcache_cache_bypass (one -> regmap , false);
1079-
10801082 /* Update LCR register */
10811083 sc16is7xx_port_write (port , SC16IS7XX_LCR_REG , lcr );
10821084
1083- mutex_unlock (& one -> efr_lock );
1085+ /* Update EFR registers */
1086+ sc16is7xx_efr_lock (port );
1087+ sc16is7xx_port_write (port , SC16IS7XX_XON1_REG , termios -> c_cc [VSTART ]);
1088+ sc16is7xx_port_write (port , SC16IS7XX_XOFF1_REG , termios -> c_cc [VSTOP ]);
1089+ sc16is7xx_port_update (port , SC16IS7XX_EFR_REG ,
1090+ SC16IS7XX_EFR_FLOWCTRL_BITS , flow );
1091+ sc16is7xx_efr_unlock (port );
10841092
10851093 /* Get baud rate generator configuration */
10861094 baud = uart_get_baud_rate (port , termios , old ,
0 commit comments