diff --git a/share/man/man4/nctgpio.4 b/share/man/man4/nctgpio.4 index 54a0bdc2df2b..ff565319089c 100644 --- a/share/man/man4/nctgpio.4 +++ b/share/man/man4/nctgpio.4 @@ -1,45 +1,55 @@ .\" $FreeBSD$ .\" -.Dd Jan 11, 2021 +.Dd Apr 18, 2023 .Dt NCTGPIO 4 .Os .Sh NAME .Nm nctgpio -.Nd GPIO controller on Nuvoton and Winbond Super I/O +.Nd GPIO controller on Nuvoton and Winbond Super I/Os .Sh SYNOPSIS .Cd "device gpio" .Cd "device nctgpio" .Cd "device superio" .Sh DESCRIPTION The .Nm is a driver for GPIO controller that can be found in Nuvoton and Winbond Super I/O chips. .Pp The .Nm driver supports the following chips: .Pp .Bl -bullet -compact .It Nuvoton NCT5104D .It Nuvoton NCT5104D (PC-Engines APU) .It Nuvoton NCT5104D (PC-Engines APU3) +.It +Nuvoton NCT5585D +.It +Nuvoton NCT6116D +.It +Nuvoton NCT6779 +.It +Nuvoton NCT6796D-E +.It +Winbond 83627DHG .El .Sh SEE ALSO .Xr gpio 3 , .Xr gpio 4 , .Xr gpioctl 8 .Sh HISTORY The driver first appeared in .Fx 11.0 . And the manual page first appeared in -.Fx 13.0 . +.Fx 14.0 . .Sh AUTHORS The driver was initially written by .An Daniel Wyatt Aq Mt daniel@dewyatt.com . This man page was written by .An Stéphane Rochoy Aq Mt stephane.rochoy@stormshield.eu . diff --git a/share/man/man4/superio.4 b/share/man/man4/superio.4 index 548b93b713f8..2608f5331b05 100644 --- a/share/man/man4/superio.4 +++ b/share/man/man4/superio.4 @@ -1,145 +1,151 @@ .\" .\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2019 Andriy Gapon .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" .Dd October 11, 2019 .Dt SUPERIO 4 .Os .Sh NAME .Nm superio .Nd Super I/O controller and bus driver .Sh SYNOPSIS To compile this driver into the kernel, place the following line in your kernel configuration file: .Bd -ragged -offset indent .Cd "device superio" .Ed .Pp Alternatively, to load the driver as a module at boot time, place the following line in .Xr loader.conf 5 : .Bd -literal -offset indent superio_load="YES" .Ed .Sh DESCRIPTION Super I/O is an I/O controller that combines various low-bandwidth devices that can be functionally unrelated otherwise. A typical Super I/O can contain devices such as .Bl -bullet -compact .It a floppy disk controller .It a parallel port .It a serial port .It a PS/2 mouse and keyboard controller .It a hardware monitoring controller .It a watchdog timer .It a controller for general purpose input-output .El .Pp The .Nm driver provides support for devices residing in the Super I/O controller that can only be accessed or discovered using the controller's interface. Some of the Super I/O devices have standardized interfaces. Such devices either use well-known legacy resources or they are advertised via ACPI or both. They can be configured either using ISA bus hints or they are auto-configured by .Xr acpi 4 . The .Nm driver is not designed to interact with that kind of devices. They can be handled by their respective drivers without any knowledge of the Super I/O specifics. For instance, .Xr fdc 4 provides access to the floppy disk controller. .Pp There are other Super I/O devices that do not have any standardized interface. Drivers for those devices can be written using facilities of the .Nm driver. .Pp The driver itself attaches to the ISA bus as all supported controllers are accessed via LPC I/O ports. .Pp The .Nm driver is unusual as it is both a controller driver for a variety of Super I/O controllers and a bus driver for supported devices in those controllers. .Sh HARDWARE The .Nm driver supports a multitude of Super I/O controllers produced by Nuvoton, formerly known as Winbond, and ITE. Namely: .Bl -bullet -compact .It Fintek F81803 .It Fintek F81865 .It Nuvoton NCT5104D/NCT6102D/NCT6106D rev. A and B+ .It +Nuvoton NCT5585D +.It +Nuvoton NCT6116D +.It Nuvoton NCT6775 .It Nuvoton NCT6776 .It Nuvoton NCT6779 .It Nuvoton NCT6791 .It Nuvoton NCT6792 .It Nuvoton NCT6793 .It Nuvoton NCT6795 .It +Nuvoton NCT6796D-E +.It Winbond 83627HF/F/HG/G/S/THF/EHF/DHG/UHG/DHG-P .It Winbond 83637HF .It Winbond 83667HG/HG-B .It Winbond 83687THF .It Winbond 83697HF/UG .El .Sh SEE ALSO .Xr superio 9 .Sh HISTORY The .Nm driver was written by .An Andriy Gapon Aq Mt avg@FreeBSD.org . diff --git a/sys/dev/nctgpio/nctgpio.c b/sys/dev/nctgpio/nctgpio.c index 607a5f3e56cf..fc3de033968f 100644 --- a/sys/dev/nctgpio/nctgpio.c +++ b/sys/dev/nctgpio/nctgpio.c @@ -1,1013 +1,1520 @@ /*- * Copyright (c) 2016 Daniel Wyatt * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ * */ /* * Nuvoton GPIO driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #define NCT_PPOD_LDN 0xf /* LDN used to select Push-Pull/Open-Drain */ /* Direct access through GPIO register table */ #define NCT_IO_GSR 0 /* Group Select */ #define NCT_IO_IOR 1 /* I/O */ #define NCT_IO_DAT 2 /* Data */ #define NCT_IO_INV 3 /* Inversion */ #define NCT_IO_DST 4 /* Status */ #define NCT_MAX_GROUP 9 #define NCT_MAX_PIN 75 #define NCT_PIN_IS_VALID(_sc, _p) ((_p) < (_sc)->npins) #define NCT_PIN_GROUP(_sc, _p) ((_sc)->pinmap[(_p)].group) #define NCT_PIN_GRPNUM(_sc, _p) ((_sc)->pinmap[(_p)].grpnum) #define NCT_PIN_BIT(_sc, _p) ((_sc)->pinmap[(_p)].bit) #define NCT_PIN_BITMASK(_p) (1 << ((_p) & 7)) #define NCT_GPIO_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | \ GPIO_PIN_INVIN | GPIO_PIN_INVOUT) #define NCT_PREFER_INDIRECT_CHANNEL 2 #define NCT_VERBOSE_PRINTF(dev, ...) \ do { \ if (__predict_false(bootverbose)) \ device_printf(dev, __VA_ARGS__); \ } while (0) /* * Note that the values are important. * They match actual register offsets. */ typedef enum { REG_IOR = 0, REG_DAT = 1, REG_INV = 2, } reg_t; struct nct_gpio_group { uint32_t caps; uint8_t enable_ldn; uint8_t enable_reg; uint8_t enable_mask; uint8_t data_ldn; uint8_t iobase; uint8_t ppod_reg; /* Push-Pull/Open-Drain */ uint8_t grpnum; uint8_t pinbits[8]; uint8_t npins; }; struct nct_softc { device_t dev; device_t busdev; struct mtx mtx; struct resource *iores; int iorid; int curgrp; struct { uint8_t ior[NCT_MAX_GROUP + 1]; /* direction, 1: input 0: output */ uint8_t out[NCT_MAX_GROUP + 1]; /* output value */ uint8_t out_known[NCT_MAX_GROUP + 1]; /* whether out is valid */ uint8_t inv[NCT_MAX_GROUP + 1]; /* inversion, 1: inverted */ } cache; struct gpio_pin pins[NCT_MAX_PIN + 1]; struct nct_device *nctdevp; int npins; /* Total number of pins */ /* Lookup tables */ struct { struct nct_gpio_group *group; uint8_t grpnum; uint8_t bit; } pinmap[NCT_MAX_PIN+1]; struct nct_gpio_group *grpmap[NCT_MAX_GROUP+1]; }; #define GPIO_LOCK_INIT(_sc) mtx_init(&(_sc)->mtx, \ device_get_nameunit(dev), NULL, MTX_DEF) #define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->mtx) #define GPIO_LOCK(_sc) mtx_lock(&(_sc)->mtx) #define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx) #define GPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_OWNED) #define GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_NOTOWNED) #define GET_BIT(v, b) (((v) >> (b)) & 1) /* * For most devices there are several GPIO devices, we attach only to one of * them and use the rest without attaching. */ struct nct_device { uint16_t devid; int extid; const char *descr; int ngroups; struct nct_gpio_group groups[NCT_MAX_GROUP + 1]; } nct_devices[] = { + { + .devid = 0xa025, + .descr = "GPIO on Winbond 83627DHG IC ver. 5", + .ngroups = 5, + .groups = { + { + .grpnum = 2, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x09, + .ppod_reg = 0xe0, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe3, + }, + { + .grpnum = 3, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + { + .grpnum = 4, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 5, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 6, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + }, + }, { .devid = 0x1061, .descr = "GPIO on Nuvoton NCT5104D", .ngroups = 2, .groups = { { .grpnum = 0, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x01, .data_ldn = 0x07, .ppod_reg = 0xe0, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe0, }, { .grpnum = 1, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x02, .data_ldn = 0x07, .ppod_reg = 0xe1, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe4, }, }, }, { - .devid = 0xc452, + .devid = 0xc452, /* FIXME Conflict with Nuvoton NCT6106D. See NetBSD's nct_match. */ .descr = "GPIO on Nuvoton NCT5104D (PC-Engines APU)", .ngroups = 2, .groups = { { .grpnum = 0, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x01, .data_ldn = 0x07, .ppod_reg = 0xe0, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe0, }, { .grpnum = 1, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x02, .data_ldn = 0x07, .ppod_reg = 0xe1, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe4, }, }, }, { .devid = 0xc453, .descr = "GPIO on Nuvoton NCT5104D (PC-Engines APU3)", .ngroups = 2, .groups = { { .grpnum = 0, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x01, .data_ldn = 0x07, .ppod_reg = 0xe0, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe0, }, { .grpnum = 1, .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, .enable_ldn = 0x07, .enable_reg = 0x30, .enable_mask = 0x02, .data_ldn = 0x07, .ppod_reg = 0xe1, .caps = NCT_GPIO_CAPS, .npins = 8, .iobase = 0xe4, }, }, }, + { + .devid = 0xd42a, + .extid = 1, + .descr = "GPIO on Nuvoton NCT6796D-E", + .ngroups = 10, + .groups = { + { + .grpnum = 0, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x08, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x08, + .ppod_reg = 0xe0, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 1, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x08, + .enable_reg = 0x30, + .enable_mask = 0x80, + .data_ldn = 0x08, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + { + .grpnum = 2, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 3, + .pinbits = { 0, 1, 2, 3, 4, 5, 6 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 7, + .iobase = 0xe4, + }, + { + .grpnum = 4, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, /* FIXME Page 344 say "F0~F2, E8", + not "F0~F3". */ + }, + { + .grpnum = 5, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 6, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 7, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 8, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe4, + }, + { + .grpnum = 9, + .pinbits = { 0, 1, 2, 3 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 4, + .iobase = 0xe8, + }, + }, + }, + { + .devid = 0xd42a, + .extid = 2, + .descr = "GPIO on Nuvoton NCT5585D", + .ngroups = 6, + .groups = { + { + .grpnum = 2, + .pinbits = { 0, 1, 2, 3, 4, 5, 6 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x09, + .ppod_reg = 0xe1, + .caps = NCT_GPIO_CAPS, + .npins = 7, + .iobase = 0xe0, + }, + { + .grpnum = 3, + .pinbits = { 1, 2, 3 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x09, + .ppod_reg = 0xe2, + .caps = NCT_GPIO_CAPS, + .npins = 3, + .iobase = 0xe4, + }, + { + .grpnum = 5, + .pinbits = { 0, 2, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x09, + .ppod_reg = 0xe4, + .caps = NCT_GPIO_CAPS, + .npins = 4, + .iobase = 0xf4, + }, + { + .grpnum = 7, + .pinbits = { 4 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x07, + .ppod_reg = 0xe6, + .caps = NCT_GPIO_CAPS, + .npins = 1, + .iobase = 0xe0, + }, + { + .grpnum = 8, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x07, + .ppod_reg = 0xe7, + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe4, + }, + { + .grpnum = 9, + .pinbits = { 0, 2 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x07, + .ppod_reg = 0xea, + .caps = NCT_GPIO_CAPS, + .npins = 2, + .iobase = 0xe8, + }, + }, + }, + { + .devid = 0xc562, + .descr = "GPIO on Nuvoton NCT6779D", + .ngroups = 9, + .groups = { + { + .grpnum = 0, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x08, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x08, + .ppod_reg = 0xe0, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 1, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x08, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + { + .grpnum = 2, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 3, + .pinbits = { 0, 1, 2, 3, 4, 5, 6 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 7, + .iobase = 0xe4, + }, + { + .grpnum = 4, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + { + .grpnum = 5, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 6, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 7, + .pinbits = { 0, 1, 2, 3, 4, 5, 6 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 7, + .iobase = 0xe0, + }, + { + .grpnum = 8, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe4, + }, + }, + }, + { + .devid = 0xd282, + .descr = "GPIO on Nuvoton NCT6112D/NCT6114D/NCT6116D", + .ngroups = 2, + .groups = { + { + .grpnum = 0, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x07, + .ppod_reg = 0xe0, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe0, + }, + { + .grpnum = 1, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x02, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe4, + }, + { + .grpnum = 2, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x04, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xe8, + }, + { + .grpnum = 3, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x08, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xec, + }, + { + .grpnum = 4, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x10, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + { + .grpnum = 5, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x20, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf4, + }, + { + .grpnum = 6, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x40, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf8, + }, + { + .grpnum = 7, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x07, + .enable_reg = 0x30, + .enable_mask = 0x80, + .data_ldn = 0x07, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xfc, + }, + { + .grpnum = 8, + .pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 }, + .enable_ldn = 0x09, + .enable_reg = 0x30, + .enable_mask = 0x01, + .data_ldn = 0x09, + .ppod_reg = 0xe1, /* FIXME Need to check for this group. */ + .caps = NCT_GPIO_CAPS, + .npins = 8, + .iobase = 0xf0, + }, + }, + }, }; static const char * io2str(uint8_t ioport) { switch (ioport) { case NCT_IO_GSR: return ("grpsel"); case NCT_IO_IOR: return ("io"); case NCT_IO_DAT: return ("data"); case NCT_IO_INV: return ("inv"); case NCT_IO_DST: return ("status"); default: return ("?"); } } static void nct_io_set_group(struct nct_softc *sc, uint8_t grpnum) { GPIO_ASSERT_LOCKED(sc); if (grpnum == sc->curgrp) return; NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x ioport %d\n", io2str(NCT_IO_GSR), grpnum, NCT_IO_GSR); bus_write_1(sc->iores, NCT_IO_GSR, grpnum); sc->curgrp = grpnum; } static uint8_t nct_io_read(struct nct_softc *sc, uint8_t grpnum, uint8_t reg) { uint8_t val; nct_io_set_group(sc, grpnum); val = bus_read_1(sc->iores, reg); NCT_VERBOSE_PRINTF(sc->dev, "read %s 0x%x ioport %d\n", io2str(reg), val, reg); return (val); } static void nct_io_write(struct nct_softc *sc, uint8_t grpnum, uint8_t reg, uint8_t val) { nct_io_set_group(sc, grpnum); NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x ioport %d\n", io2str(reg), val, reg); bus_write_1(sc->iores, reg, val); } static uint8_t nct_get_ioreg(struct nct_softc *sc, reg_t reg, uint8_t grpnum) { uint8_t iobase; if (sc->iores != NULL) iobase = NCT_IO_IOR; else iobase = sc->grpmap[grpnum]->iobase; return (iobase + reg); } static const char * reg2str(reg_t reg) { switch (reg) { case REG_IOR: return ("io"); case REG_DAT: return ("data"); case REG_INV: return ("inv"); default: return ("?"); } } static uint8_t nct_read_reg(struct nct_softc *sc, reg_t reg, uint8_t grpnum) { struct nct_gpio_group *gp; uint8_t ioreg; uint8_t val; ioreg = nct_get_ioreg(sc, reg, grpnum); if (sc->iores != NULL) return (nct_io_read(sc, grpnum, ioreg)); gp = sc->grpmap[grpnum]; val = superio_ldn_read(sc->dev, gp->data_ldn, ioreg); NCT_VERBOSE_PRINTF(sc->dev, "read %s 0x%x from group GPIO%u ioreg 0x%x\n", reg2str(reg), val, grpnum, ioreg); return (val); } static int nct_get_pin_cache(struct nct_softc *sc, uint32_t pin_num, uint8_t *cache) { uint8_t bit; uint8_t group; uint8_t val; KASSERT(NCT_PIN_IS_VALID(sc, pin_num), ("%s: invalid pin number %d", __func__, pin_num)); group = NCT_PIN_GRPNUM(sc, pin_num); bit = NCT_PIN_BIT(sc, pin_num); val = cache[group]; return (GET_BIT(val, bit)); } static void nct_write_reg(struct nct_softc *sc, reg_t reg, uint8_t grpnum, uint8_t val) { struct nct_gpio_group *gp; uint8_t ioreg; ioreg = nct_get_ioreg(sc, reg, grpnum); if (sc->iores != NULL) { nct_io_write(sc, grpnum, ioreg, val); return; } gp = sc->grpmap[grpnum]; superio_ldn_write(sc->dev, gp->data_ldn, ioreg, val); NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x to group GPIO%u ioreg 0x%x\n", reg2str(reg), val, grpnum, ioreg); } static void nct_set_pin_reg(struct nct_softc *sc, reg_t reg, uint32_t pin_num, bool val) { uint8_t *cache; uint8_t bit; uint8_t bitval; uint8_t group; uint8_t mask; KASSERT(NCT_PIN_IS_VALID(sc, pin_num), ("%s: invalid pin number %d", __func__, pin_num)); KASSERT(reg == REG_IOR || reg == REG_INV, ("%s: unsupported register %d", __func__, reg)); group = NCT_PIN_GRPNUM(sc, pin_num); bit = NCT_PIN_BIT(sc, pin_num); mask = (uint8_t)1 << bit; bitval = (uint8_t)val << bit; if (reg == REG_IOR) cache = &sc->cache.ior[group]; else cache = &sc->cache.inv[group]; if ((*cache & mask) == bitval) return; *cache &= ~mask; *cache |= bitval; nct_write_reg(sc, reg, group, *cache); } /* * Set a pin to input (val is true) or output (val is false) mode. */ static void nct_set_pin_input(struct nct_softc *sc, uint32_t pin_num, bool val) { nct_set_pin_reg(sc, REG_IOR, pin_num, val); } /* * Check whether a pin is configured as an input. */ static bool nct_pin_is_input(struct nct_softc *sc, uint32_t pin_num) { return (nct_get_pin_cache(sc, pin_num, sc->cache.ior)); } /* * Set a pin to inverted (val is true) or normal (val is false) mode. */ static void nct_set_pin_inverted(struct nct_softc *sc, uint32_t pin_num, bool val) { nct_set_pin_reg(sc, REG_INV, pin_num, val); } static bool nct_pin_is_inverted(struct nct_softc *sc, uint32_t pin_num) { return (nct_get_pin_cache(sc, pin_num, sc->cache.inv)); } /* * Write a value to an output pin. * NB: the hardware remembers last output value across switching from * output mode to input mode and back. * Writes to a pin in input mode are not allowed here as they cannot * have any effect and would corrupt the output value cache. */ static void nct_write_pin(struct nct_softc *sc, uint32_t pin_num, bool val) { uint8_t bit; uint8_t group; KASSERT(!nct_pin_is_input(sc, pin_num), ("attempt to write input pin")); group = NCT_PIN_GRPNUM(sc, pin_num); bit = NCT_PIN_BIT(sc, pin_num); if (GET_BIT(sc->cache.out_known[group], bit) && GET_BIT(sc->cache.out[group], bit) == val) { /* The pin is already in requested state. */ return; } sc->cache.out_known[group] |= 1 << bit; if (val) sc->cache.out[group] |= 1 << bit; else sc->cache.out[group] &= ~(1 << bit); nct_write_reg(sc, REG_DAT, group, sc->cache.out[group]); } static bool nct_get_pin_reg(struct nct_softc *sc, reg_t reg, uint32_t pin_num) { uint8_t bit; uint8_t group; uint8_t val; bool b; KASSERT(NCT_PIN_IS_VALID(sc, pin_num), ("%s: invalid pin number %d", __func__, pin_num)); group = NCT_PIN_GRPNUM(sc, pin_num); bit = NCT_PIN_BIT(sc, pin_num); val = nct_read_reg(sc, reg, group); b = GET_BIT(val, bit); if (__predict_false(bootverbose)) { if (nct_pin_is_input(sc, pin_num)) NCT_VERBOSE_PRINTF(sc->dev, "read %d from input pin %u\n", b, pin_num, group, bit); else NCT_VERBOSE_PRINTF(sc->dev, "read %d from output pin %u, cache miss\n", b, pin_num, group, bit); } return (b); } /* * NB: state of an input pin cannot be cached, of course. * For an output we can either take the value from the cache if it's valid * or read the state from the hadrware and cache it. */ static bool nct_read_pin(struct nct_softc *sc, uint32_t pin_num) { uint8_t bit; uint8_t group; bool val; if (nct_pin_is_input(sc, pin_num)) { return (nct_get_pin_reg(sc, REG_DAT, pin_num)); } group = NCT_PIN_GRPNUM(sc, pin_num); bit = NCT_PIN_BIT(sc, pin_num); if (GET_BIT(sc->cache.out_known[group], bit)) { val = GET_BIT(sc->cache.out[group], bit); NCT_VERBOSE_PRINTF(sc->dev, "read %d from output pin %u, cache hit\n", val, pin_num, group, bit); return (val); } val = nct_get_pin_reg(sc, REG_DAT, pin_num); sc->cache.out_known[group] |= 1 << bit; if (val) sc->cache.out[group] |= 1 << bit; else sc->cache.out[group] &= ~(1 << bit); return (val); } +/* FIXME Incorret for NCT5585D and probably other chips. */ static uint8_t nct_ppod_reg(struct nct_softc *sc, uint32_t pin_num) { uint8_t group = NCT_PIN_GRPNUM(sc, pin_num); return (sc->grpmap[group]->ppod_reg); } /* * NB: PP/OD can be configured only via configuration registers. * Also, the registers are in a different logical device. * So, this is a special case. No caching too. */ static void nct_set_pin_opendrain(struct nct_softc *sc, uint32_t pin_num) { uint8_t reg; uint8_t outcfg; reg = nct_ppod_reg(sc, pin_num); outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg); outcfg |= NCT_PIN_BITMASK(pin_num); superio_ldn_write(sc->dev, 0xf, reg, outcfg); } static void nct_set_pin_pushpull(struct nct_softc *sc, uint32_t pin_num) { uint8_t reg; uint8_t outcfg; reg = nct_ppod_reg(sc, pin_num); outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg); outcfg &= ~NCT_PIN_BITMASK(pin_num); superio_ldn_write(sc->dev, 0xf, reg, outcfg); } static bool nct_pin_is_opendrain(struct nct_softc *sc, uint32_t pin_num) { uint8_t reg; uint8_t outcfg; reg = nct_ppod_reg(sc, pin_num); outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg); return (outcfg & NCT_PIN_BITMASK(pin_num)); } static struct nct_device * nct_lookup_device(device_t dev) { struct nct_device *nctdevp; uint16_t devid; int i, extid; devid = superio_devid(dev); extid = superio_extid(dev); for (i = 0, nctdevp = nct_devices; i < nitems(nct_devices); i++, nctdevp++) { if (devid == nctdevp->devid && nctdevp->extid == extid) return (nctdevp); } return (NULL); } static int nct_probe(device_t dev) { struct nct_device *nctdevp; uint8_t ldn; ldn = superio_get_ldn(dev); if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON) { NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not a Nuvoton device\n", ldn); return (ENXIO); } if (superio_get_type(dev) != SUPERIO_DEV_GPIO) { NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not a GPIO device\n", ldn); return (ENXIO); } nctdevp = nct_lookup_device(dev); if (nctdevp == NULL) { NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not supported\n", ldn); return (ENXIO); } device_set_desc(dev, nctdevp->descr); return (BUS_PROBE_DEFAULT); } static int nct_attach(device_t dev) { struct nct_softc *sc; struct nct_gpio_group *gp; uint32_t pin_num; uint8_t v; int flags, i, g; sc = device_get_softc(dev); sc->dev = dev; sc->nctdevp = nct_lookup_device(dev); flags = 0; (void)resource_int_value(device_get_name(dev), device_get_unit(dev), "flags", &flags); if ((flags & NCT_PREFER_INDIRECT_CHANNEL) == 0) { uint16_t iobase; device_t dev_8; /* * As strange as it may seem, I/O port base is configured in the * Logical Device 8 which is primarily used for WDT, but also plays * a role in GPIO configuration. */ iobase = 0; dev_8 = superio_find_dev(device_get_parent(dev), SUPERIO_DEV_WDT, 8); if (dev_8 != NULL) iobase = superio_get_iobase(dev_8); if (iobase != 0 && iobase != 0xffff) { int err; NCT_VERBOSE_PRINTF(dev, "iobase %#x\n", iobase); sc->curgrp = -1; sc->iorid = 0; err = bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid, - iobase, 7); + iobase, 7); /* FIXME NCT6796D-E have 8 registers according to table 18.3. */ if (err == 0) { sc->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->iorid, RF_ACTIVE); if (sc->iores == NULL) { device_printf(dev, "can't map i/o space, " "iobase=%#x\n", iobase); } } else { device_printf(dev, "failed to set io port resource at %#x\n", iobase); } } } NCT_VERBOSE_PRINTF(dev, "iores %p %s channel\n", sc->iores, (sc->iores ? "direct" : "indirect")); /* Enable GPIO groups */ for (g = 0, gp = sc->nctdevp->groups; g < sc->nctdevp->ngroups; g++, gp++) { NCT_VERBOSE_PRINTF(dev, "GPIO%d: %d pins, enable with mask 0x%x via ldn 0x%x reg 0x%x\n", gp->grpnum, gp->npins, gp->enable_mask, gp->enable_ldn, gp->enable_reg); v = superio_ldn_read(dev, gp->enable_ldn, gp->enable_reg); v |= gp->enable_mask; superio_ldn_write(dev, gp->enable_ldn, gp->enable_reg, v); } GPIO_LOCK_INIT(sc); GPIO_LOCK(sc); pin_num = 0; sc->npins = 0; for (g = 0, gp = sc->nctdevp->groups; g < sc->nctdevp->ngroups; g++, gp++) { sc->npins += gp->npins; for (i = 0; i < gp->npins; i++, pin_num++) { struct gpio_pin *pin; sc->pinmap[pin_num].group = gp; sc->pinmap[pin_num].grpnum = gp->grpnum; sc->pinmap[pin_num].bit = gp->pinbits[i]; sc->grpmap[gp->grpnum] = gp; pin = &sc->pins[pin_num]; pin->gp_pin = pin_num; pin->gp_caps = gp->caps; pin->gp_flags = 0; snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u%u", gp->grpnum, gp->pinbits[i]); if (nct_pin_is_input(sc, pin_num)) pin->gp_flags |= GPIO_PIN_INPUT; else pin->gp_flags |= GPIO_PIN_OUTPUT; if (nct_pin_is_opendrain(sc, pin_num)) pin->gp_flags |= GPIO_PIN_OPENDRAIN; else pin->gp_flags |= GPIO_PIN_PUSHPULL; if (nct_pin_is_inverted(sc, pin_num)) pin->gp_flags |= (GPIO_PIN_INVIN | GPIO_PIN_INVOUT); } } NCT_VERBOSE_PRINTF(dev, "%d pins available\n", sc->npins); /* * Caching input values is meaningless as an input can be changed at any * time by an external agent. But outputs are controlled by this * driver, so it can cache their state. Also, the hardware remembers * the output state of a pin when the pin is switched to input mode and * then back to output mode. So, the cache stays valid. * The only problem is with pins that are in input mode at the attach * time. For them the output state is not known until it is set by the * driver for the first time. * 'out' and 'out_known' bits form a tri-state output cache: * |-----+-----------+---------| * | out | out_known | cache | * |-----+-----------+---------| * | X | 0 | invalid | * | 0 | 1 | 0 | * | 1 | 1 | 1 | * |-----+-----------+---------| */ for (g = 0, gp = sc->nctdevp->groups; g < sc->nctdevp->ngroups; g++, gp++) { sc->cache.inv[gp->grpnum] = nct_read_reg(sc, REG_INV, gp->grpnum); sc->cache.ior[gp->grpnum] = nct_read_reg(sc, REG_IOR, gp->grpnum); sc->cache.out[gp->grpnum] = nct_read_reg(sc, REG_DAT, gp->grpnum); sc->cache.out_known[gp->grpnum] = ~sc->cache.ior[gp->grpnum]; } GPIO_UNLOCK(sc); sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { device_printf(dev, "failed to attach to gpiobus\n"); GPIO_LOCK_DESTROY(sc); return (ENXIO); } return (0); } static int nct_detach(device_t dev) { struct nct_softc *sc; sc = device_get_softc(dev); gpiobus_detach_bus(dev); if (sc->iores != NULL) bus_release_resource(dev, SYS_RES_IOPORT, sc->iorid, sc->iores); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK_DESTROY(sc); return (0); } static device_t nct_gpio_get_bus(device_t dev) { struct nct_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int nct_gpio_pin_max(device_t dev, int *maxpin) { struct nct_softc *sc; sc = device_get_softc(dev); *maxpin = sc->npins - 1; return (0); } static int nct_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_LOCK(sc); if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) { GPIO_UNLOCK(sc); return (EINVAL); } nct_write_pin(sc, pin_num, pin_value); GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); *pin_value = nct_read_pin(sc, pin_num); GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_toggle(device_t dev, uint32_t pin_num) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) { GPIO_UNLOCK(sc); return (EINVAL); } if (nct_read_pin(sc, pin_num)) nct_write_pin(sc, pin_num, 0); else nct_write_pin(sc, pin_num, 1); GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); *caps = sc->pins[pin_num].gp_caps; GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); *flags = sc->pins[pin_num].gp_flags; GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_getname(device_t dev, uint32_t pin_num, char *name) { struct nct_softc *sc; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); memcpy(name, sc->pins[pin_num].gp_name, GPIOMAXNAME); GPIO_UNLOCK(sc); return (0); } static int nct_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags) { struct nct_softc *sc; struct gpio_pin *pin; sc = device_get_softc(dev); if (!NCT_PIN_IS_VALID(sc, pin_num)) return (EINVAL); pin = &sc->pins[pin_num]; if ((flags & pin->gp_caps) != flags) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { return (EINVAL); } if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) == (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) { return (EINVAL); } if ((flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) == (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) { return (EINVAL); } GPIO_ASSERT_UNLOCKED(sc); GPIO_LOCK(sc); /* input or output */ if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) != 0) { nct_set_pin_input(sc, pin_num, (flags & GPIO_PIN_INPUT) != 0); pin->gp_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); pin->gp_flags |= flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); } /* invert */ if ((flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) != 0) { nct_set_pin_inverted(sc, pin_num, 1); pin->gp_flags |= (GPIO_PIN_INVIN | GPIO_PIN_INVOUT); } else { nct_set_pin_inverted(sc, pin_num, 0); pin->gp_flags &= ~(GPIO_PIN_INVIN | GPIO_PIN_INVOUT); } /* Open drain or push pull */ if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) != 0) { if (flags & GPIO_PIN_OPENDRAIN) nct_set_pin_opendrain(sc, pin_num); else nct_set_pin_pushpull(sc, pin_num); pin->gp_flags &= ~(GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL); pin->gp_flags |= flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL); } GPIO_UNLOCK(sc); return (0); } static device_method_t nct_methods[] = { /* Device interface */ DEVMETHOD(device_probe, nct_probe), DEVMETHOD(device_attach, nct_attach), DEVMETHOD(device_detach, nct_detach), /* GPIO */ DEVMETHOD(gpio_get_bus, nct_gpio_get_bus), DEVMETHOD(gpio_pin_max, nct_gpio_pin_max), DEVMETHOD(gpio_pin_get, nct_gpio_pin_get), DEVMETHOD(gpio_pin_set, nct_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, nct_gpio_pin_toggle), DEVMETHOD(gpio_pin_getname, nct_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, nct_gpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, nct_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, nct_gpio_pin_setflags), DEVMETHOD_END }; static driver_t nct_driver = { "gpio", nct_methods, sizeof(struct nct_softc) }; DRIVER_MODULE(nctgpio, superio, nct_driver, NULL, NULL); MODULE_DEPEND(nctgpio, gpiobus, 1, 1, 1); MODULE_DEPEND(nctgpio, superio, 1, 1, 1); MODULE_VERSION(nctgpio, 1); diff --git a/sys/dev/superio/superio.c b/sys/dev/superio/superio.c index 6db2b0685c1c..2d40d9ef43b7 100644 --- a/sys/dev/superio/superio.c +++ b/sys/dev/superio/superio.c @@ -1,1077 +1,1115 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 Andriy Gapon * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "isa_if.h" typedef void (*sio_conf_enter_f)(struct resource*, uint16_t); typedef void (*sio_conf_exit_f)(struct resource*, uint16_t); struct sio_conf_methods { sio_conf_enter_f enter; sio_conf_exit_f exit; superio_vendor_t vendor; }; struct sio_device { uint8_t ldn; superio_dev_type_t type; }; struct superio_devinfo { STAILQ_ENTRY(superio_devinfo) link; struct resource_list resources; device_t dev; uint8_t ldn; superio_dev_type_t type; uint16_t iobase; uint16_t iobase2; uint8_t irq; uint8_t dma; }; struct siosc { struct mtx conf_lock; STAILQ_HEAD(, superio_devinfo) devlist; struct resource* io_res; struct cdev *chardev; int io_rid; uint16_t io_port; const struct sio_conf_methods *methods; const struct sio_device *known_devices; superio_vendor_t vendor; uint16_t devid; uint8_t revid; int extid; uint8_t current_ldn; uint8_t ldn_reg; uint8_t enable_reg; }; static d_ioctl_t superio_ioctl; static struct cdevsw superio_cdevsw = { .d_version = D_VERSION, .d_ioctl = superio_ioctl, .d_name = "superio", }; #define NUMPORTS 2 static uint8_t sio_read(struct resource* res, uint8_t reg) { bus_write_1(res, 0, reg); return (bus_read_1(res, 1)); } /* Read a word from two one-byte registers, big endian. */ static uint16_t sio_readw(struct resource* res, uint8_t reg) { uint16_t v; v = sio_read(res, reg); v <<= 8; v |= sio_read(res, reg + 1); return (v); } static void sio_write(struct resource* res, uint8_t reg, uint8_t val) { bus_write_1(res, 0, reg); bus_write_1(res, 1, val); } static void sio_ldn_select(struct siosc *sc, uint8_t ldn) { mtx_assert(&sc->conf_lock, MA_OWNED); if (ldn == sc->current_ldn) return; sio_write(sc->io_res, sc->ldn_reg, ldn); sc->current_ldn = ldn; } static uint8_t sio_ldn_read(struct siosc *sc, uint8_t ldn, uint8_t reg) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg >= sc->enable_reg) { sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); } return (sio_read(sc->io_res, reg)); } static uint16_t sio_ldn_readw(struct siosc *sc, uint8_t ldn, uint8_t reg) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg >= sc->enable_reg) { sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); } return (sio_readw(sc->io_res, reg)); } static void sio_ldn_write(struct siosc *sc, uint8_t ldn, uint8_t reg, uint8_t val) { mtx_assert(&sc->conf_lock, MA_OWNED); if (reg <= sc->ldn_reg) { printf("ignored attempt to write special register 0x%x\n", reg); return; } sio_ldn_select(sc, ldn); KASSERT(sc->current_ldn == ldn, ("sio_ldn_select failed")); sio_write(sc->io_res, reg, val); } static void sio_conf_enter(struct siosc *sc) { mtx_lock(&sc->conf_lock); sc->methods->enter(sc->io_res, sc->io_port); } static void sio_conf_exit(struct siosc *sc) { sc->methods->exit(sc->io_res, sc->io_port); sc->current_ldn = 0xff; mtx_unlock(&sc->conf_lock); } static void ite_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x01); bus_write_1(res, 0, 0x55); bus_write_1(res, 0, port == 0x2e ? 0x55 : 0xaa); } static void ite_conf_exit(struct resource* res, uint16_t port) { sio_write(res, 0x02, 0x02); } static const struct sio_conf_methods ite_conf_methods = { .enter = ite_conf_enter, .exit = ite_conf_exit, .vendor = SUPERIO_VENDOR_ITE }; static void nvt_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x87); } static void nvt_conf_exit(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0xaa); } static const struct sio_conf_methods nvt_conf_methods = { .enter = nvt_conf_enter, .exit = nvt_conf_exit, .vendor = SUPERIO_VENDOR_NUVOTON }; static void fintek_conf_enter(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0x87); bus_write_1(res, 0, 0x87); } static void fintek_conf_exit(struct resource* res, uint16_t port) { bus_write_1(res, 0, 0xaa); } static const struct sio_conf_methods fintek_conf_methods = { .enter = fintek_conf_enter, .exit = fintek_conf_exit, .vendor = SUPERIO_VENDOR_FINTEK }; static const struct sio_conf_methods * const methods_table[] = { &ite_conf_methods, &nvt_conf_methods, &fintek_conf_methods, NULL }; static const uint16_t ports_table[] = { 0x2e, 0x4e, 0 }; const struct sio_device ite_devices[] = { { .ldn = 4, .type = SUPERIO_DEV_HWM }, { .ldn = 7, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; +const struct sio_device w83627_devices[] = { + { .ldn = 8, .type = SUPERIO_DEV_WDT }, + { .ldn = 9, .type = SUPERIO_DEV_GPIO }, + { .type = SUPERIO_DEV_NONE }, +}; + const struct sio_device nvt_devices[] = { { .ldn = 8, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; const struct sio_device nct5104_devices[] = { { .ldn = 7, .type = SUPERIO_DEV_GPIO }, { .ldn = 8, .type = SUPERIO_DEV_WDT }, { .ldn = 15, .type = SUPERIO_DEV_GPIO }, { .type = SUPERIO_DEV_NONE }, }; +const struct sio_device nct5585_devices[] = { + { .ldn = 9, .type = SUPERIO_DEV_GPIO }, + { .type = SUPERIO_DEV_NONE }, +}; + +const struct sio_device nct611x_devices[] = { + { .ldn = 0x7, .type = SUPERIO_DEV_GPIO }, + { .ldn = 0x8, .type = SUPERIO_DEV_WDT }, + { .type = SUPERIO_DEV_NONE }, +}; + +const struct sio_device nct67xx_devices[] = { + { .ldn = 0x8, .type = SUPERIO_DEV_WDT }, + { .ldn = 0x9, .type = SUPERIO_DEV_GPIO }, + { .type = SUPERIO_DEV_NONE }, +}; + const struct sio_device fintek_devices[] = { { .ldn = 6, .type = SUPERIO_DEV_GPIO }, { .ldn = 7, .type = SUPERIO_DEV_WDT }, { .type = SUPERIO_DEV_NONE }, }; static const struct { superio_vendor_t vendor; uint16_t devid; uint16_t mask; int extid; /* Extra ID: used to handle conflicting devid. */ const char *descr; const struct sio_device *devices; } superio_table[] = { { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8613, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8712, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8716, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8718, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8720, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8721, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8726, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8728, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_ITE, .devid = 0x8771, .devices = ite_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x1061, .mask = 0x00, .descr = "Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. A)", .devices = nct5104_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x5200, .mask = 0xff, .descr = "Winbond 83627HF/F/HG/G", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x5900, .mask = 0xff, .descr = "Winbond 83627S", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x6000, .mask = 0xff, .descr = "Winbond 83697HF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x6800, .mask = 0xff, .descr = "Winbond 83697UG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x7000, .mask = 0xff, .descr = "Winbond 83637HF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8200, .mask = 0xff, .descr = "Winbond 83627THF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8500, .mask = 0xff, .descr = "Winbond 83687THF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0x8800, .mask = 0xff, .descr = "Winbond 83627EHF", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa000, .mask = 0xff, .descr = "Winbond 83627DHG", - .devices = nvt_devices, + .devices = w83627_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa200, .mask = 0xff, .descr = "Winbond 83627UHG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xa500, .mask = 0xff, .descr = "Winbond 83667HG", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb000, .mask = 0xff, .descr = "Winbond 83627DHG-P", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb300, .mask = 0xff, .descr = "Winbond 83667HG-B", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xb400, .mask = 0xff, .descr = "Nuvoton NCT6775", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc300, .mask = 0xff, .descr = "Nuvoton NCT6776", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc400, .mask = 0xff, .descr = "Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. B+)", .devices = nct5104_devices, }, { - .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc500, .mask = 0xff, - .descr = "Nuvoton NCT6779", - .devices = nvt_devices, + .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc500, .mask = 0xff, + .descr = "Nuvoton NCT6779D", + .devices = nct67xx_devices, + }, + { + .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd42a, .extid = 1, + .descr = "Nuvoton NCT6796D-E", + .devices = nct67xx_devices, + }, + { + .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd42a, .extid = 2, + .descr = "Nuvoton NCT5585D", + .devices = nct5585_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc800, .mask = 0xff, .descr = "Nuvoton NCT6791", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xc900, .mask = 0xff, .descr = "Nuvoton NCT6792", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd100, .mask = 0xff, .descr = "Nuvoton NCT6793", .devices = nvt_devices, }, + { + .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd200, .mask = 0xff, + .descr = "Nuvoton NCT6112D/NCT6114D/NCT6116D", + .devices = nct611x_devices, + }, { .vendor = SUPERIO_VENDOR_NUVOTON, .devid = 0xd300, .mask = 0xff, .descr = "Nuvoton NCT6795", .devices = nvt_devices, }, { .vendor = SUPERIO_VENDOR_FINTEK, .devid = 0x1210, .mask = 0xff, .descr = "Fintek F81803", .devices = fintek_devices, }, { .vendor = SUPERIO_VENDOR_FINTEK, .devid = 0x0704, .descr = "Fintek F81865", .devices = fintek_devices, }, { 0, 0 } }; static const char * devtype_to_str(superio_dev_type_t type) { switch (type) { case SUPERIO_DEV_NONE: return ("none"); case SUPERIO_DEV_HWM: return ("HWM"); case SUPERIO_DEV_WDT: return ("WDT"); case SUPERIO_DEV_GPIO: return ("GPIO"); case SUPERIO_DEV_MAX: return ("invalid"); } return ("invalid"); } static int superio_detect(device_t dev, bool claim, struct siosc *sc) { struct resource *res; rman_res_t port; rman_res_t count; uint16_t devid; uint8_t revid; int error; int rid; int i, m; int prefer; error = bus_get_resource(dev, SYS_RES_IOPORT, 0, &port, &count); if (error != 0) return (error); if (port > UINT16_MAX || count < NUMPORTS) { device_printf(dev, "unexpected I/O range size\n"); return (ENXIO); } /* * Make a temporary resource reservation for hardware probing. * If we can't get the resources we need then * we need to abort. Possibly this indicates * the resources were used by another device * in which case the probe would have failed anyhow. */ rid = 0; res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (res == NULL) { if (claim) device_printf(dev, "failed to allocate I/O resource\n"); return (ENXIO); } prefer = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "prefer", &prefer); if (bootverbose && prefer > 0) device_printf(dev, "prefer extid %d\n", prefer); for (m = 0; methods_table[m] != NULL; m++) { methods_table[m]->enter(res, port); if (methods_table[m]->vendor == SUPERIO_VENDOR_ITE) { devid = sio_readw(res, 0x20); revid = sio_read(res, 0x22); } else if (methods_table[m]->vendor == SUPERIO_VENDOR_NUVOTON) { devid = sio_read(res, 0x20); revid = sio_read(res, 0x21); devid = (devid << 8) | revid; } else if (methods_table[m]->vendor == SUPERIO_VENDOR_FINTEK) { devid = sio_read(res, 0x20); revid = sio_read(res, 0x21); devid = (devid << 8) | revid; } else { continue; } methods_table[m]->exit(res, port); for (i = 0; superio_table[i].vendor != 0; i++) { uint16_t mask; mask = superio_table[i].mask; if (superio_table[i].vendor != methods_table[m]->vendor) continue; if ((superio_table[i].devid & ~mask) != (devid & ~mask)) continue; if (prefer > 0 && prefer != superio_table[i].extid) continue; break; } /* Found a matching SuperIO entry. */ if (superio_table[i].vendor != 0) break; } if (methods_table[m] == NULL) error = ENXIO; else error = 0; if (!claim || error != 0) { bus_release_resource(dev, SYS_RES_IOPORT, rid, res); return (error); } sc->methods = methods_table[m]; sc->vendor = sc->methods->vendor; sc->known_devices = superio_table[i].devices; sc->io_res = res; sc->io_rid = rid; sc->io_port = port; sc->devid = devid; sc->revid = revid; sc->extid = superio_table[i].extid; KASSERT(sc->vendor == SUPERIO_VENDOR_ITE || sc->vendor == SUPERIO_VENDOR_NUVOTON || sc->vendor == SUPERIO_VENDOR_FINTEK, ("Only ITE, Nuvoton and Fintek SuperIO-s are supported")); sc->ldn_reg = 0x07; sc->enable_reg = 0x30; /* FIXME enable_reg not used by nctgpio(4). */ sc->current_ldn = 0xff; /* no device should have this */ if (superio_table[i].descr != NULL) { device_set_desc(dev, superio_table[i].descr); } else if (sc->vendor == SUPERIO_VENDOR_ITE) { char descr[64]; snprintf(descr, sizeof(descr), "ITE IT%4x SuperIO (revision 0x%02x)", sc->devid, sc->revid); device_set_desc_copy(dev, descr); } return (0); } static void superio_identify(driver_t *driver, device_t parent) { device_t child; int i; /* * Don't create child devices if any already exist. * Those could be created via isa hints or if this * driver is loaded, unloaded and then loaded again. */ if (device_find_child(parent, "superio", -1)) { if (bootverbose) printf("superio: device(s) already created\n"); return; } /* * Create a child for each candidate port. * It would be nice if we could somehow clean up those * that this driver fails to probe. */ for (i = 0; ports_table[i] != 0; i++) { child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "superio", -1); if (child == NULL) { device_printf(parent, "failed to add superio child\n"); continue; } bus_set_resource(child, SYS_RES_IOPORT, 0, ports_table[i], 2); if (superio_detect(child, false, NULL) != 0) device_delete_child(parent, child); } } static int superio_probe(device_t dev) { struct siosc *sc; int error; /* Make sure we do not claim some ISA PNP device. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); /* * XXX We can populate the softc now only because we return * BUS_PROBE_SPECIFIC */ sc = device_get_softc(dev); error = superio_detect(dev, true, sc); if (error != 0) return (error); return (BUS_PROBE_SPECIFIC); } static void superio_add_known_child(device_t dev, superio_dev_type_t type, uint8_t ldn) { struct siosc *sc = device_get_softc(dev); struct superio_devinfo *dinfo; device_t child; child = BUS_ADD_CHILD(dev, 0, NULL, -1); if (child == NULL) { device_printf(dev, "failed to add child for ldn %d, type %s\n", ldn, devtype_to_str(type)); return; } dinfo = device_get_ivars(child); dinfo->ldn = ldn; dinfo->type = type; sio_conf_enter(sc); dinfo->iobase = sio_ldn_readw(sc, ldn, 0x60); dinfo->iobase2 = sio_ldn_readw(sc, ldn, 0x62); dinfo->irq = sio_ldn_readw(sc, ldn, 0x70); dinfo->dma = sio_ldn_readw(sc, ldn, 0x74); sio_conf_exit(sc); STAILQ_INSERT_TAIL(&sc->devlist, dinfo, link); } static int superio_attach(device_t dev) { struct siosc *sc = device_get_softc(dev); int i; mtx_init(&sc->conf_lock, device_get_nameunit(dev), "superio", MTX_DEF); STAILQ_INIT(&sc->devlist); for (i = 0; sc->known_devices[i].type != SUPERIO_DEV_NONE; i++) { superio_add_known_child(dev, sc->known_devices[i].type, sc->known_devices[i].ldn); } bus_generic_probe(dev); bus_generic_attach(dev); sc->chardev = make_dev(&superio_cdevsw, device_get_unit(dev), UID_ROOT, GID_WHEEL, 0600, "superio%d", device_get_unit(dev)); if (sc->chardev == NULL) device_printf(dev, "failed to create character device\n"); else sc->chardev->si_drv1 = sc; return (0); } static int superio_detach(device_t dev) { struct siosc *sc = device_get_softc(dev); int error; error = bus_generic_detach(dev); if (error != 0) return (error); if (sc->chardev != NULL) destroy_dev(sc->chardev); device_delete_children(dev); bus_release_resource(dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res); mtx_destroy(&sc->conf_lock); return (0); } static device_t superio_add_child(device_t dev, u_int order, const char *name, int unit) { struct superio_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_NOWAIT | M_ZERO); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } dinfo->ldn = 0xff; dinfo->type = SUPERIO_DEV_NONE; dinfo->dev = child; resource_list_init(&dinfo->resources); device_set_ivars(child, dinfo); return (child); } static int superio_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct superio_devinfo *dinfo; dinfo = device_get_ivars(child); switch (which) { case SUPERIO_IVAR_LDN: *result = dinfo->ldn; break; case SUPERIO_IVAR_TYPE: *result = dinfo->type; break; case SUPERIO_IVAR_IOBASE: *result = dinfo->iobase; break; case SUPERIO_IVAR_IOBASE2: *result = dinfo->iobase2; break; case SUPERIO_IVAR_IRQ: *result = dinfo->irq; break; case SUPERIO_IVAR_DMA: *result = dinfo->dma; break; default: return (ENOENT); } return (0); } static int superio_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { switch (which) { case SUPERIO_IVAR_LDN: case SUPERIO_IVAR_TYPE: case SUPERIO_IVAR_IOBASE: case SUPERIO_IVAR_IOBASE2: case SUPERIO_IVAR_IRQ: case SUPERIO_IVAR_DMA: return (EINVAL); default: return (ENOENT); } } static struct resource_list * superio_get_resource_list(device_t dev, device_t child) { struct superio_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static int superio_printf(struct superio_devinfo *dinfo, const char *fmt, ...) { va_list ap; int retval; retval = printf("superio:%s@ldn%0x2x: ", devtype_to_str(dinfo->type), dinfo->ldn); va_start(ap, fmt); retval += vprintf(fmt, ap); va_end(ap); return (retval); } static void superio_child_detached(device_t dev, device_t child) { struct superio_devinfo *dinfo; struct resource_list *rl; dinfo = device_get_ivars(child); rl = &dinfo->resources; if (resource_list_release_active(rl, dev, child, SYS_RES_IRQ) != 0) superio_printf(dinfo, "Device leaked IRQ resources\n"); if (resource_list_release_active(rl, dev, child, SYS_RES_MEMORY) != 0) superio_printf(dinfo, "Device leaked memory resources\n"); if (resource_list_release_active(rl, dev, child, SYS_RES_IOPORT) != 0) superio_printf(dinfo, "Device leaked I/O resources\n"); } static int superio_child_location(device_t parent, device_t child, struct sbuf *sb) { uint8_t ldn; ldn = superio_get_ldn(child); sbuf_printf(sb, "ldn=0x%02x", ldn); return (0); } static int superio_child_pnp(device_t parent, device_t child, struct sbuf *sb) { superio_dev_type_t type; type = superio_get_type(child); sbuf_printf(sb, "type=%s", devtype_to_str(type)); return (0); } static int superio_print_child(device_t parent, device_t child) { superio_dev_type_t type; uint8_t ldn; int retval; ldn = superio_get_ldn(child); type = superio_get_type(child); retval = bus_print_child_header(parent, child); retval += printf(" at %s ldn 0x%02x", devtype_to_str(type), ldn); retval += bus_print_child_footer(parent, child); return (retval); } superio_vendor_t superio_vendor(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->vendor); } uint16_t superio_devid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->devid); } uint8_t superio_revid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->revid); } int superio_extid(device_t dev) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); return (sc->extid); } uint8_t superio_ldn_read(device_t dev, uint8_t ldn, uint8_t reg) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); uint8_t v; sio_conf_enter(sc); v = sio_ldn_read(sc, ldn, reg); sio_conf_exit(sc); return (v); } uint8_t superio_read(device_t dev, uint8_t reg) { struct superio_devinfo *dinfo = device_get_ivars(dev); return (superio_ldn_read(dev, dinfo->ldn, reg)); } void superio_ldn_write(device_t dev, uint8_t ldn, uint8_t reg, uint8_t val) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); sio_conf_enter(sc); sio_ldn_write(sc, ldn, reg, val); sio_conf_exit(sc); } void superio_write(device_t dev, uint8_t reg, uint8_t val) { struct superio_devinfo *dinfo = device_get_ivars(dev); return (superio_ldn_write(dev, dinfo->ldn, reg, val)); } bool superio_dev_enabled(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return (true); v = superio_read(dev, sc->enable_reg); /* FIXME enable_reg not used by nctgpio(4). */ return ((v & mask) != 0); } void superio_dev_enable(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return; sio_conf_enter(sc); v = sio_ldn_read(sc, dinfo->ldn, sc->enable_reg); v |= mask; sio_ldn_write(sc, dinfo->ldn, sc->enable_reg, v); sio_conf_exit(sc); } void superio_dev_disable(device_t dev, uint8_t mask) { device_t sio_dev = device_get_parent(dev); struct siosc *sc = device_get_softc(sio_dev); struct superio_devinfo *dinfo = device_get_ivars(dev); uint8_t v; /* GPIO device is always active in ITE chips. */ if (sc->vendor == SUPERIO_VENDOR_ITE && dinfo->ldn == 7) return; sio_conf_enter(sc); v = sio_ldn_read(sc, dinfo->ldn, sc->enable_reg); v &= ~mask; sio_ldn_write(sc, dinfo->ldn, sc->enable_reg, v); sio_conf_exit(sc); } device_t superio_find_dev(device_t superio, superio_dev_type_t type, int ldn) { struct siosc *sc = device_get_softc(superio); struct superio_devinfo *dinfo; if (ldn < -1 || ldn > UINT8_MAX) return (NULL); /* ERANGE */ if (type == SUPERIO_DEV_NONE && ldn == -1) return (NULL); /* EINVAL */ STAILQ_FOREACH(dinfo, &sc->devlist, link) { if (ldn != -1 && dinfo->ldn != ldn) continue; if (type != SUPERIO_DEV_NONE && dinfo->type != type) continue; return (dinfo->dev); } return (NULL); } static int superio_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { struct siosc *sc; struct superiocmd *s; sc = dev->si_drv1; s = (struct superiocmd *)data; switch (cmd) { case SUPERIO_CR_READ: sio_conf_enter(sc); s->val = sio_ldn_read(sc, s->ldn, s->cr); sio_conf_exit(sc); return (0); case SUPERIO_CR_WRITE: sio_conf_enter(sc); sio_ldn_write(sc, s->ldn, s->cr, s->val); sio_conf_exit(sc); return (0); default: return (ENOTTY); } } static device_method_t superio_methods[] = { DEVMETHOD(device_identify, superio_identify), DEVMETHOD(device_probe, superio_probe), DEVMETHOD(device_attach, superio_attach), DEVMETHOD(device_detach, superio_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(bus_add_child, superio_add_child), DEVMETHOD(bus_child_detached, superio_child_detached), DEVMETHOD(bus_child_location, superio_child_location), DEVMETHOD(bus_child_pnpinfo, superio_child_pnp), DEVMETHOD(bus_print_child, superio_print_child), DEVMETHOD(bus_read_ivar, superio_read_ivar), DEVMETHOD(bus_write_ivar, superio_write_ivar), DEVMETHOD(bus_get_resource_list, superio_get_resource_list), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t superio_driver = { "superio", superio_methods, sizeof(struct siosc) }; DRIVER_MODULE(superio, isa, superio_driver, 0, 0); MODULE_VERSION(superio, 1);