Index: sys/dev/puc/puc.c =================================================================== --- sys/dev/puc/puc.c +++ sys/dev/puc/puc.c @@ -49,25 +49,6 @@ #include #include -#define PUC_ISRCCNT 5 - -struct puc_port { - struct puc_bar *p_bar; - struct resource *p_rres; - struct resource *p_ires; - device_t p_dev; - int p_nr; - int p_type; - int p_rclk; - - int p_hasintr:1; - - serdev_intr_t *p_ihsrc[PUC_ISRCCNT]; - void *p_iharg; - - int p_ipend; -}; - devclass_t puc_devclass; const char puc_driver_name[] = "puc"; @@ -728,6 +709,34 @@ return (ENOENT); } return (0); +} + +int +puc_bus_write_ivar(device_t dev, device_t child, int index, uintptr_t val) +{ + struct puc_port *port; + struct puc_softc *sc; + intptr_t *varr; + + /* Get our immediate child. */ + while (child != NULL && device_get_parent(child) != dev) + child = device_get_parent(child); + if (child == NULL) + return (EINVAL); + + port = device_get_ivars(child); + KASSERT(port != NULL, ("%s %d", __func__, __LINE__)); + + switch(index) { + case PUC_IVAR_TARGETBAUD: + sc = device_get_softc(dev); + if (sc && sc->sc_cfg && sc->sc_cfg->config_function) { + varr = (intptr_t *)val; + return sc->sc_cfg->config_function(sc, PUC_CFG_TARGETBAUD, port->p_nr - 1, varr); + } + return EOPNOTSUPP; + } + return ENOENT; } int Index: sys/dev/puc/puc_bfe.h =================================================================== --- sys/dev/puc/puc_bfe.h +++ sys/dev/puc/puc_bfe.h @@ -31,10 +31,11 @@ #ifndef _DEV_PUC_BFE_H_ #define _DEV_PUC_BFE_H_ +#include + #define PUC_PCI_BARS 6 struct puc_cfg; -struct puc_port; extern const struct puc_cfg puc_pci_devices[]; @@ -47,6 +48,27 @@ int b_type; }; +#define PUC_ISRCCNT 5 + +struct puc_port { + struct puc_bar *p_bar; + struct resource *p_rres; + struct resource *p_ires; + device_t p_dev; + int p_nr; + int p_type; + int p_rclk; + + int p_hasintr:1; + + serdev_intr_t *p_ihsrc[PUC_ISRCCNT]; + void *p_iharg; + + int p_ipend; +}; + +struct puc_bar *puc_get_bar(struct puc_softc *sc, int rid); + struct puc_softc { device_t sc_dev; @@ -79,8 +101,6 @@ u_long sc_serdevs; }; -struct puc_bar *puc_get_bar(struct puc_softc *sc, int rid); - int puc_bfe_attach(device_t); int puc_bfe_detach(device_t); int puc_bfe_probe(device_t, const struct puc_cfg *); @@ -92,6 +112,7 @@ int puc_bus_get_resource(device_t, device_t, int, int, rman_res_t *, rman_res_t *); int puc_bus_print_child(device_t, device_t); int puc_bus_read_ivar(device_t, device_t, int, uintptr_t *); +int puc_bus_write_ivar(device_t, device_t, int, uintptr_t); int puc_bus_release_resource(device_t, device_t, int, int, struct resource *); int puc_bus_setup_intr(device_t, device_t, struct resource *, int, driver_filter_t *, driver_intr_t *, void *, void **); Index: sys/dev/puc/puc_bus.h =================================================================== --- sys/dev/puc/puc_bus.h +++ sys/dev/puc/puc_bus.h @@ -36,6 +36,7 @@ #define PUC_IVAR_CLOCK 0 #define PUC_IVAR_TYPE 1 +#define PUC_IVAR_TARGETBAUD 2 /* Port types. */ #define PUC_TYPE_SERIAL 1 Index: sys/dev/puc/puc_cfg.h =================================================================== --- sys/dev/puc/puc_cfg.h +++ sys/dev/puc/puc_cfg.h @@ -64,7 +64,8 @@ PUC_CFG_GET_OFS, PUC_CFG_GET_RID, PUC_CFG_GET_TYPE, - PUC_CFG_SETUP + PUC_CFG_SETUP, + PUC_CFG_TARGETBAUD }; struct puc_softc; Index: sys/dev/puc/puc_cfg.c =================================================================== --- sys/dev/puc/puc_cfg.c +++ sys/dev/puc/puc_cfg.c @@ -172,6 +172,9 @@ case PUC_CFG_SETUP: *r = ENXIO; return (0); + case PUC_CFG_TARGETBAUD: + *r = ENXIO; + return (0); } return (ENXIO); Index: sys/dev/puc/puc_pccard.c =================================================================== --- sys/dev/puc/puc_pccard.c +++ sys/dev/puc/puc_pccard.c @@ -83,6 +83,7 @@ DEVMETHOD(bus_release_resource, puc_bus_release_resource), DEVMETHOD(bus_get_resource, puc_bus_get_resource), DEVMETHOD(bus_read_ivar, puc_bus_read_ivar), + DEVMETHOD(bus_write_ivar, puc_bus_write_ivar), DEVMETHOD(bus_setup_intr, puc_bus_setup_intr), DEVMETHOD(bus_teardown_intr, puc_bus_teardown_intr), DEVMETHOD(bus_print_child, puc_bus_print_child), Index: sys/dev/puc/puc_pci.c =================================================================== --- sys/dev/puc/puc_pci.c +++ sys/dev/puc/puc_pci.c @@ -183,6 +183,7 @@ DEVMETHOD(bus_release_resource, puc_bus_release_resource), DEVMETHOD(bus_get_resource, puc_bus_get_resource), DEVMETHOD(bus_read_ivar, puc_bus_read_ivar), + DEVMETHOD(bus_write_ivar, puc_bus_write_ivar), DEVMETHOD(bus_setup_intr, puc_bus_setup_intr), DEVMETHOD(bus_teardown_intr, puc_bus_teardown_intr), DEVMETHOD(bus_print_child, puc_bus_print_child), Index: sys/dev/puc/pucdata.c =================================================================== --- sys/dev/puc/pucdata.c +++ sys/dev/puc/pucdata.c @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include @@ -1754,14 +1756,180 @@ return (ENXIO); } +struct puc_util_fract { + uint64_t n; + uint64_t d; +}; + +/* + * Returns the greatest common denomiator of a and b + * Thanks Wikipedia! + */ +static uint64_t +puc_util_gcd(uint64_t a, uint64_t b) +{ + uint64_t d = 0; + + while (((a | b) & 1) == 0) { + a >>= 1; + b >>= 1; + d++; + } + while (a != b) { + if ((a & 1) == 0) + a >>= 1; + else if ((b & 1) == 0) + b >>= 1; + else if (a > b) + a = (a - b) >> 1; + else + b = (b - a) >> 1; + } + return (1<n, f->d); + + f->n /= d; + f->d /= d; +} + +/* + * Divides two fractions + * r may equal x or y. + */ +static void +puc_util_fract_div(struct puc_util_fract *x, struct puc_util_fract *y, + struct puc_util_fract *r) +{ + uint64_t rn, rd; + + rn = x->n * y->d; + rd = x->d * y->n; + r->n = rn; + r->d = rd; + puc_util_fract_reduce(r); +} + +struct puc_oxford_1695x_result { + uint16_t cpr; + uint16_t div; + uint8_t tcr; +}; + static int +puc_oxford_1695x_solve(struct puc_util_fract *baud, + struct puc_oxford_1695x_result *res) +{ + struct puc_util_fract base = {62500000, 16}; + struct puc_util_fract prescale; + struct puc_util_fract result; + + int tcr; + int div; + int cpr; + int bcpr = INT_MAX; + int bdiv = INT_MAX; + int btcr; + int act; + int64_t err; + int64_t berr = INT64_MAX; + + /* First, try for "perfect" just using div */ + puc_util_fract_div(&base, baud, &result); + + /* We got it... done. */ + if (result.d == 1) { + if (result.n < 65536 && result.n > 0) { + res->tcr = 16; + res->cpr = 8; + res->div = result.n; + return 0; + } + } + + /* + * Now we try all combinations of tcr/cpr, picking the best one. + * We prefer high tcr, and we may still get a perfect match. + * This results in a table similar to the one in the datasheet, + * but with a preference for a high tcr and low prescaler. + * The high tcr may make it more resistant to timing errors, + * and the low prescaler shouldn't matter. + */ + for (tcr = 16; tcr >= 4; tcr--) { + base.d = tcr; + for (cpr = 8; cpr < 512; cpr++) { + prescale.n = cpr; + prescale.d = 8; + puc_util_fract_div(&base, &prescale, &prescale); + puc_util_fract_div(&prescale, baud, &result); + + /* We got a perfect match. */ + if (result.d == 1) { + if (result.n < 65536 && result.n > 0) { + res->tcr = tcr; + res->cpr = cpr; + res->div = result.n; + return 0; + } + } + div = ((result.n << 1) / result.d + 1) >> 1; + if (div > 65535) + continue; + if (div < 1) + continue; + act = (((base.n << 1) / div + 1) / base.d) >> 1; + err = (((baud->n << 1) / baud->d + 1) >> 1) - act; + + if (err < 0) + err = -err; + if (err < berr) { + bcpr = cpr; + bdiv = div; + berr = err; + btcr = tcr; + } + } + } + + res->tcr = btcr; + res->cpr = bcpr; + res->div = bdiv; + + /* + * Check for errors... + */ + if (res->tcr < 4 || res->tcr > 16 || + res->cpr < 8 || res->cpr > 511 || + res->div < 1 || res->div > 65535) { + res->tcr = 16; + res->cpr = 8; + res->div = 65535; + return EINVAL; + } + + return 0; +} + +static int puc_config_oxford_pcie(struct puc_softc *sc, enum puc_cfg_cmd cmd, int port, intptr_t *res) { const struct puc_cfg *cfg = sc->sc_cfg; + struct puc_oxford_1695x_result bres; + struct puc_util_fract baud; + struct baud_fraction *bf; int idx; struct puc_bar *bar; uint8_t value; + uint16_t prescale; + int rval; switch (cmd) { case PUC_CFG_SETUP: @@ -1820,6 +1988,87 @@ return (0); case PUC_CFG_GET_TYPE: *res = PUC_TYPE_SERIAL; + return (0); + case PUC_CFG_TARGETBAUD: + bf = (struct baud_fraction *)res; + bar = puc_get_bar(sc, cfg->rid); + if (bar == NULL) + return (ENXIO); + baud.n = bf->bf_numerator; + baud.d = bf->bf_denominator; + rval = puc_oxford_1695x_solve(&baud, &bres); + if (rval) + return rval; + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0x04); + if (bres.cpr == 8) { + value &= ~0x80; + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0x04, + value); + } + else { + value |= 0x80; + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0x04, + value); + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0xC1, + bres.cpr & 0xff); + /* Don't change unused bits */ + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0xC3); + value &= 0xfe; + value |= (bres.cpr >> 8) & 1; + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0xC3, + value); + } + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0xC2); + value &= 0xf0; + if (bres.tcr == 16) + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0xC2, value); + else + bus_write_1(bar->b_res, 0x1000 + (port << 9) + 0xC2, + value | (bres.tcr & 0x0f)); + sc->sc_port[port].p_rclk = 62500000; // Base rclk + sc->sc_port[port].p_rclk <<= 4; // Assumed tcr + sc->sc_port[port].p_rclk /= bres.tcr; // Real tcr + sc->sc_port[port].p_rclk <<= 3; // Prescale units 1/8 + sc->sc_port[port].p_rclk /= bres.cpr; + return (0); + case PUC_CFG_GET_CLOCK: + *res = 62500000; // Base clock is 62.5MHz + /* + * TCR sets the data-bit oversampling... UART driver + * assumes this is 16, so we scale the clock up if less + * than 16. + */ + *res <<= 4; + bar = puc_get_bar(sc, cfg->rid); + if (bar == NULL) + return (ENXIO); + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0xC2); + value &= 0x0f; + if (value < 4) + *res >>= 4; + else + *res /= value; + /* + * Now check MCR bit 7... if it's set, the prescaler + * is enabled. + */ + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0x04); + if ((value & 0x80) == 0) + return 0; + + /* + * Read the prescaler configuration + */ + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0xC3); + value &= 1; + prescale = value<<8; + value = bus_read_1(bar->b_res, 0x1000 + (port << 9) + 0xC1); + prescale |= value; + if (prescale < 8) + return ENXIO; + *res <<= 3; + *res /= prescale; + return (0); default: break; Index: sys/dev/uart/uart_bus.h =================================================================== --- sys/dev/uart/uart_bus.h +++ sys/dev/uart/uart_bus.h @@ -37,6 +37,7 @@ #include #include +#include /* Drain and flush targets. */ #define UART_DRAIN_RECEIVER 0x0001 @@ -55,6 +56,8 @@ #define UART_IOCTL_IFLOW 2 #define UART_IOCTL_OFLOW 3 #define UART_IOCTL_BAUD 4 +#define UART_IOCTL_SET_FBAUD 5 +#define UART_IOCTL_GET_FBAUD 6 /* * UART class & instance (=softc) Index: sys/dev/uart/uart_bus_puc.c =================================================================== --- sys/dev/uart/uart_bus_puc.c +++ sys/dev/uart/uart_bus_puc.c @@ -45,7 +45,10 @@ #include #include +#include "uart_if.h" + static int uart_puc_probe(device_t dev); +static void uart_puc_adjust_rclk(device_t dev, void *bptr); static device_method_t uart_puc_methods[] = { /* Device interface */ @@ -55,6 +58,7 @@ /* Serdev interface */ DEVMETHOD(serdev_ihand, uart_bus_ihand), DEVMETHOD(serdev_ipend, uart_bus_ipend), + DEVMETHOD(serdev_adjust_rclk, uart_puc_adjust_rclk), { 0, 0 } }; @@ -84,6 +88,20 @@ if (BUS_READ_IVAR(parent, dev, PUC_IVAR_CLOCK, &rclk)) rclk = 0; return (uart_bus_probe(dev, 0, 0, rclk, 0, 0)); +} + +static void +uart_puc_adjust_rclk(device_t dev, void *bptr) +{ + struct baud_fraction *baud = bptr; + struct uart_softc *sc = device_get_softc(dev); + device_t parent; + uintptr_t rclk; + + parent = device_get_parent(sc->sc_dev); + BUS_WRITE_IVAR(parent, sc->sc_dev, PUC_IVAR_TARGETBAUD, (uintptr_t)baud); + if (BUS_READ_IVAR(parent, sc->sc_dev, PUC_IVAR_CLOCK, &rclk) == 0) + sc->sc_bas.rclk = rclk; } DRIVER_MODULE(uart, puc, uart_puc_driver, uart_devclass, 0, 0); Index: sys/dev/uart/uart_dev_ns8250.c =================================================================== --- sys/dev/uart/uart_dev_ns8250.c +++ sys/dev/uart/uart_dev_ns8250.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #ifdef FDT @@ -144,7 +145,32 @@ return (divisor); } +/* + * As above, but takes a fractional baudrate. + * This one currently does NOT enforce a maximum deviation. + */ static int +ns8250_fdivisor(int rclk, int baudrate_n, int baudrate_d) +{ + int divisor; + int divisor_n, divisor_d; + + if (baudrate_n == 0 || baudrate_d == 0) + return (0); + + divisor_n = rclk * baudrate_d; + divisor_d = baudrate_n; + + divisor = (divisor_n / (divisor_d << 3) + 1) >> 1; + if (divisor <= 0 || divisor > 65535) + return (0); + + /* TODO: Enforce max 3% error in actual baudrate */ + + return (divisor); +} + +static int ns8250_drain(struct uart_bas *bas, int what) { int delay, limit; @@ -610,6 +636,7 @@ struct uart_bas *bas; int baudrate, divisor, error; uint8_t efr, lcr; + struct baud_fraction *bf; bas = &sc->sc_bas; error = 0; @@ -669,6 +696,46 @@ else error = ENXIO; break; + case UART_IOCTL_SET_FBAUD: { + /* Set baudrate. */ + bf = (struct baud_fraction *)data; + + divisor = ns8250_fdivisor(bas->rclk, bf->bf_numerator, bf->bf_denominator); + if (divisor <= 0 || divisor > 65535) { + error = EINVAL; + break; + } + lcr = uart_getreg(bas, REG_LCR); + uart_setreg(bas, REG_LCR, lcr | LCR_DLAB); + uart_barrier(bas); + uart_setreg(bas, REG_DLL, divisor & 0xff); + uart_setreg(bas, REG_DLH, (divisor >> 8) & 0xff); + uart_barrier(bas); + uart_setreg(bas, REG_LCR, lcr); + uart_barrier(bas); + sc->sc_u.u_tty.tp->t_termios.c_ispeed = ((bas->rclk / (divisor << 3)) + 1) >> 1; + sc->sc_u.u_tty.tp->t_termios.c_ospeed = sc->sc_u.u_tty.tp->t_termios.c_ispeed; + break; + case UART_IOCTL_GET_FBAUD: { + bf = (struct baud_fraction *)data; + + lcr = uart_getreg(bas, REG_LCR); + uart_setreg(bas, REG_LCR, lcr | LCR_DLAB); + uart_barrier(bas); + divisor = uart_getreg(bas, REG_DLL) | + (uart_getreg(bas, REG_DLH) << 8); + uart_barrier(bas); + uart_setreg(bas, REG_LCR, lcr); + uart_barrier(bas); + if (divisor <= 0) + error = ENXIO; + else { + bf->bf_numerator = bas->rclk; + bf->bf_denominator = divisor << 4; + } + break; + } + } default: error = EINVAL; break; Index: sys/dev/uart/uart_tty.c =================================================================== --- sys/dev/uart/uart_tty.c +++ sys/dev/uart/uart_tty.c @@ -49,6 +49,8 @@ #include #include +#include + #include "uart_if.h" static cn_probe_t uart_cnprobe; @@ -251,6 +253,13 @@ case TIOCCBRK: UART_IOCTL(sc, UART_IOCTL_BREAK, 0); return (0); + case TIOCSFBAUD: + SERDEV_ADJUST_RCLK(sc->sc_dev, (struct baud_fraction *)data); + UART_IOCTL(sc, UART_IOCTL_SET_FBAUD, (intptr_t)data); + return (0); + case TIOCGFBAUD: + UART_IOCTL(sc, UART_IOCTL_GET_FBAUD, (intptr_t)data); + return (0); default: return pps_ioctl(cmd, data, &sc->sc_pps); } @@ -261,6 +270,7 @@ { struct uart_softc *sc; int databits, parity, stopbits; + struct baud_fraction bf; sc = tty_softc(tp); if (sc == NULL || sc->sc_leaving) @@ -283,6 +293,9 @@ UART_PARITY_EVEN; else parity = UART_PARITY_NONE; + bf.bf_numerator = t->c_ospeed; + bf.bf_denominator = 1; + SERDEV_ADJUST_RCLK(sc->sc_dev, &bf); if (UART_PARAM(sc, t->c_ospeed, databits, stopbits, parity) != 0) return (EINVAL); UART_SETSIG(sc, SER_DDTR | SER_DTR); Index: sys/kern/serdev_if.m =================================================================== --- sys/kern/serdev_if.m +++ sys/kern/serdev_if.m @@ -60,6 +60,12 @@ { return (0); } + + static void + default_adjust_rclk(device_t dev, void *bf) + { + return; + } }; # ihand() - Query serial device interrupt handler. @@ -92,3 +98,9 @@ device_t dev; } DEFAULT default_sysdev; +# adjust_rclk() - request rclk be adjusted to allow the specified +# fractional baud rate to be configured. +METHOD void adjust_rclk { + device_t dev; + void *baudrate; +} DEFAULT default_adjust_rclk; Index: sys/kern/tty.c =================================================================== --- sys/kern/tty.c +++ sys/kern/tty.c @@ -614,6 +614,20 @@ new->c_ospeed = old->c_ospeed; } + if (cmd == TIOCSFBAUD) { + struct termios *lock = TTY_CALLOUT(tp, dev) ? + &tp->t_termios_lock_out : &tp->t_termios_lock_in; + + /* For lock state devices, just fail */ + if (lock->c_ispeed || lock->c_ospeed) { + error = EINVAL; + goto done; + } + error = tty_drain(tp, 0); + if (error) + goto done; + } + error = tty_ioctl(tp, cmd, data, fflag, td); done: tty_unlock(tp); Index: sys/sys/ttycom.h =================================================================== --- sys/sys/ttycom.h +++ sys/sys/ttycom.h @@ -58,6 +58,14 @@ unsigned short ws_ypixel; /* vertical size, pixels */ }; +/* + * Fractional baudrate. Used by TIOCSFBAUD and TIOCGFBAUD + */ +struct baud_fraction { + int bf_numerator; + int bf_denominator; +}; + /* 0-2 compat */ /* 3-7 unused */ /* 8-10 compat */ @@ -137,6 +145,8 @@ #define TIOCCBRK _IO('t', 122) /* clear break bit */ #define TIOCSBRK _IO('t', 123) /* set break bit */ /* 124-127 compat */ +#define TIOCSFBAUD _IOW('t', 128, struct baud_fraction) /* Set fractional baudrate */ +#define TIOCGFBAUD _IOR('t', 129, struct baud_fraction) /* Get fractional baudrate */ #define TTYDISC 0 /* termios tty line discipline */ #define SLIPDISC 4 /* serial IP discipline */