mirror of
https://github.com/opnsense/src.git
synced 2026-05-28 04:12:45 -04:00
Taken from: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=189772 PR: https://github.com/opnsense/core/issues/2114
685 lines
16 KiB
C
685 lines
16 KiB
C
/*-
|
|
* Copyright (c) 2014-2017 Larry Baird
|
|
* All rights reserved.
|
|
*
|
|
* Feedback provided by Ermal Luci.
|
|
*
|
|
* Used information from daduke's linux driver (https://daduke.org/linux/apu2)
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/priv.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <x86/bus.h>
|
|
#include <isa/isavar.h>
|
|
#include <dev/led/led.h>
|
|
|
|
static struct mtx gpio_lock;
|
|
MTX_SYSINIT(gpio_lock, &gpio_lock, "gpio lock", MTX_SPIN);
|
|
|
|
/*
|
|
* Basic idea is to create two MMIO memory resources. One for LEDs and
|
|
* one for button on front of APU. Then create a device for each LED and
|
|
* the button. On an apu3 also create a device for switch for switching SIMs.
|
|
*/
|
|
|
|
/* See dev/amdsbwd/amdsbwd.c for magic numbers for southbridges */
|
|
|
|
/* SB7xx RRG 2.3.3.1.1. */
|
|
#define AMDSB_PMIO_INDEX 0xcd6
|
|
#define AMDSB_PMIO_DATA (PMIO_INDEX + 1)
|
|
#define AMDSB_PMIO_WIDTH 2
|
|
|
|
#define AMDSB_SMBUS_DEVID 0x43851002
|
|
#define AMDFCH_SMBUS_DEVID 0x780b1022
|
|
|
|
/* SB8xx RRG 2.3.7. */
|
|
#define AMDSB8_MMIO_BASE_ADDR_FIND 0x24
|
|
|
|
/* Here are some magic numbers from APU1 BIOS. */
|
|
#define SB_GPIO_OFFSET 0x100
|
|
#define GPIO_187 187 // APU1 MODESW
|
|
#define GPIO_188 188 // APU1 Unknown ??
|
|
#define GPIO_189 189 // APU1 LED1#
|
|
#define GPIO_190 190 // APU1 LED2#
|
|
#define GPIO_191 191 // APU1 LED3#
|
|
|
|
#define SB_GPIO_ON 0x08
|
|
#define SB_GPIO_OFF 0xC8
|
|
|
|
/* Here are some magic numbers for APU2. */
|
|
#define AMDFCH41_MMIO_ADDR 0xfed80000u
|
|
#define FCH_GPIO_OFFSET 0x1500
|
|
#define FCH_GPIO_BASE (AMDFCH41_MMIO_ADDR + FCH_GPIO_OFFSET)
|
|
#define FCH_GPIO_SIZE 0x300
|
|
#define FCH_GPIO_BIT_WRITE 22
|
|
#define FCH_GPIO_BIT_READ 16
|
|
#define FCH_GPIO_BIT_DIR 23
|
|
#define GPIO_68 68 // APU2/3 LED1#
|
|
#define GPIO_69 69 // APU2/3 LED2#
|
|
#define GPIO_70 70 // APU2/3 LED3#
|
|
#define GPIO_89 89 // APU2/3 MODESW
|
|
#define GPIO_90 90 // APU3 SIM switcher
|
|
|
|
struct apu_cdev {
|
|
struct resource *res;
|
|
bus_size_t offset;
|
|
struct cdev *cdev;
|
|
uint32_t devid;
|
|
};
|
|
|
|
struct apu_rid {
|
|
int rid;
|
|
int rid_type;
|
|
struct resource *res;
|
|
};
|
|
|
|
struct apu_softc {
|
|
int sc_model;
|
|
uint32_t sc_devid;
|
|
struct apu_rid sc_rid[2];
|
|
# define IDX_RID_LED 0
|
|
# define IDX_RID_MODESW 1
|
|
struct apu_cdev sc_led[3];
|
|
struct apu_cdev sc_sw[2];
|
|
# define IDX_SW_MODE 0
|
|
# define IDX_SW_SIM 1
|
|
};
|
|
|
|
/*
|
|
* Mode switch methods.
|
|
*/
|
|
static int sw_open(struct cdev *dev, int flags, int fmt, struct thread *td);
|
|
static int sw_close(struct cdev *dev, int flags, int fmt, struct thread *td);
|
|
static int sw_read(struct cdev *dev, struct uio *uio, int ioflag);
|
|
static int sw_write(struct cdev *dev, struct uio *uio, int ioflag);
|
|
|
|
static void
|
|
apu_led_callback(void *ptr, int onoff);
|
|
|
|
static struct cdevsw modesw_cdev = {
|
|
.d_version = D_VERSION,
|
|
.d_open = sw_open,
|
|
.d_read = sw_read,
|
|
.d_close = sw_close,
|
|
.d_name = "modesw",
|
|
};
|
|
|
|
static struct cdevsw simsw_cdev = {
|
|
.d_version = D_VERSION,
|
|
.d_open = sw_open,
|
|
.d_read = sw_read,
|
|
.d_write = sw_write,
|
|
.d_close = sw_close,
|
|
.d_name = "simsw",
|
|
};
|
|
|
|
static int
|
|
hw_is_apu( void )
|
|
{
|
|
int apu = 0;
|
|
char *maker;
|
|
char *product;
|
|
|
|
maker = kern_getenv("smbios.system.maker");
|
|
if (maker != NULL) {
|
|
if (strcasecmp("PC Engines", maker) == 0) {
|
|
product = kern_getenv("smbios.system.product");
|
|
if (product != NULL) {
|
|
if (strcasecmp("APU", product) == 0)
|
|
apu = 1;
|
|
else if (strcasecmp("apu2", product) == 0)
|
|
apu = 2;
|
|
else if (strcasecmp("apu3", product) == 0)
|
|
apu = 3;
|
|
else if (strcasecmp("apu4", product) == 0)
|
|
apu = 4;
|
|
|
|
freeenv(product);
|
|
}
|
|
}
|
|
|
|
freeenv(maker);
|
|
}
|
|
|
|
return (apu);
|
|
}
|
|
|
|
static void
|
|
sb_gpio_write( struct resource *res, bus_size_t offset, int active )
|
|
{
|
|
u_int8_t value;
|
|
|
|
value = bus_read_1(res, offset);
|
|
|
|
if (active)
|
|
value = SB_GPIO_ON;
|
|
else
|
|
value = SB_GPIO_OFF;
|
|
|
|
bus_write_1(res, offset, value);
|
|
}
|
|
|
|
static char
|
|
sb_gpio_read( struct resource *res, bus_size_t offset )
|
|
{
|
|
uint8_t value;
|
|
char ch;
|
|
|
|
/* Is mode switch pressed? */
|
|
value = bus_read_1(res, offset);
|
|
|
|
if (value == 0x28 )
|
|
ch = '1';
|
|
else
|
|
ch = '0';
|
|
|
|
return (ch);
|
|
}
|
|
|
|
/*
|
|
* gpio methods.
|
|
*/
|
|
static void
|
|
fch_gpio_dir_set( struct resource *res, bus_size_t offset, int out )
|
|
{
|
|
u_int32_t value;
|
|
u_int32_t dir_bit = 1 << FCH_GPIO_BIT_DIR;
|
|
|
|
value = bus_read_4(res, offset);
|
|
|
|
if (out)
|
|
value |= dir_bit;
|
|
else
|
|
value &= ~dir_bit;
|
|
|
|
bus_write_4(res, offset, value);
|
|
}
|
|
|
|
static char
|
|
fch_gpio_read( struct resource *res, bus_size_t offset )
|
|
{
|
|
uint32_t value;
|
|
char ch;
|
|
u_int32_t read_bit = 1 << FCH_GPIO_BIT_READ;
|
|
|
|
/* Is mode switch pressed? */
|
|
value = bus_read_4(res, offset);
|
|
|
|
if (!(value & read_bit))
|
|
ch = '1';
|
|
else
|
|
ch = '0';
|
|
|
|
return (ch);
|
|
}
|
|
|
|
static void
|
|
fch_gpio_write( struct resource *res, bus_size_t offset, int active )
|
|
{
|
|
u_int32_t value;
|
|
u_int32_t write_bit = 1 << FCH_GPIO_BIT_WRITE;
|
|
|
|
value = bus_read_4(res, offset);
|
|
|
|
if (active)
|
|
value &= ~write_bit;
|
|
else
|
|
value |= write_bit;
|
|
|
|
bus_write_4(res, offset, value);
|
|
}
|
|
|
|
|
|
/* Check to see if this is an APU board we support? */
|
|
static void
|
|
apuled_identify(driver_t *driver, device_t parent)
|
|
{
|
|
device_t child;
|
|
device_t smb;
|
|
uint32_t devid;
|
|
|
|
if (resource_disabled("apuled", 0))
|
|
return;
|
|
|
|
if (device_find_child(parent, "apuled", -1) != NULL)
|
|
return;
|
|
|
|
/* Do was have expected south bridge chipset? */
|
|
smb = pci_find_bsf(0, 20, 0);
|
|
if (smb == NULL)
|
|
return;
|
|
|
|
devid = pci_get_devid(smb);
|
|
|
|
switch (hw_is_apu()) {
|
|
case 1:
|
|
if (devid != AMDSB_SMBUS_DEVID)
|
|
return;
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
if (devid != AMDFCH_SMBUS_DEVID)
|
|
return;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/* Everything looks good, enable probe */
|
|
child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "apuled", -1);
|
|
if (child == NULL)
|
|
device_printf(parent, "apuled: bus add child failed\n");
|
|
}
|
|
|
|
static int
|
|
apu_probe_sb(device_t dev, struct apu_softc *sc)
|
|
{
|
|
struct resource *res;
|
|
int rc;
|
|
uint32_t gpio_mmio_base;
|
|
int rid;
|
|
int i;
|
|
|
|
/* Find the ACPImmioAddr base address */
|
|
rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, AMDSB_PMIO_INDEX,
|
|
AMDSB_PMIO_WIDTH);
|
|
if (rc != 0) {
|
|
device_printf(dev, "bus_set_resource for MMIO failed\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
rid = 0;
|
|
res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0ul, ~0ul,
|
|
AMDSB_PMIO_WIDTH, RF_ACTIVE | RF_SHAREABLE);
|
|
|
|
if (res == NULL) {
|
|
device_printf(dev, "bus_alloc_resource for MMIO failed.\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Find base address of memory mapped WDT registers. */
|
|
/* This will probable be 0xfed80000 */
|
|
for (gpio_mmio_base = 0, i = 0; i < 4; i++) {
|
|
gpio_mmio_base <<= 8;
|
|
bus_write_1(res, 0, AMDSB8_MMIO_BASE_ADDR_FIND + 3 - i);
|
|
gpio_mmio_base |= bus_read_1(res, 1);
|
|
}
|
|
gpio_mmio_base &= ~0x07u;
|
|
|
|
if (bootverbose)
|
|
device_printf(dev, "MMIO base adddress 0x%x\n", gpio_mmio_base);
|
|
|
|
bus_release_resource(dev, SYS_RES_IOPORT, rid, res);
|
|
bus_delete_resource(dev, SYS_RES_IOPORT, rid);
|
|
|
|
/* Set memory resource for LEDs. */
|
|
rc = bus_set_resource(dev, SYS_RES_MEMORY, 0,
|
|
gpio_mmio_base + SB_GPIO_OFFSET + GPIO_189,
|
|
(GPIO_191 - GPIO_189) + 1);
|
|
if (rc != 0) {
|
|
device_printf(dev, "bus_set_resource for LEDs failed\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Set memory resource for switches. */
|
|
rc = bus_set_resource(dev, SYS_RES_MEMORY, 1,
|
|
gpio_mmio_base + SB_GPIO_OFFSET + GPIO_187, 1);
|
|
if (rc != 0) {
|
|
device_printf(dev, "bus_set_resource for switches failed\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
apu_probe_fch(device_t dev, struct apu_softc *sc)
|
|
{
|
|
int rc;
|
|
u_long count;
|
|
|
|
/* Set memory resource for LEDs. */
|
|
rc = bus_set_resource(dev, SYS_RES_MEMORY, 0,
|
|
FCH_GPIO_BASE + (GPIO_68 * sizeof(uint32_t)),
|
|
((GPIO_70 - GPIO_68) + 1) * sizeof(uint32_t) );
|
|
if (rc != 0) {
|
|
device_printf(dev, "bus_set_resource for LEDs failed\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Set memory resource for switches. */
|
|
if (sc->sc_model == 3)
|
|
count = sizeof(uint32_t) * 2;
|
|
else
|
|
count = sizeof(uint32_t);
|
|
rc = bus_set_resource(dev, SYS_RES_MEMORY, 1,
|
|
FCH_GPIO_BASE + (GPIO_89 * sizeof(uint32_t)), count );
|
|
if (rc != 0) {
|
|
device_printf(dev, "bus_set_resource for switches failed\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* APU LED device methods.
|
|
*/
|
|
static int
|
|
apuled_probe(device_t dev)
|
|
{
|
|
int error;
|
|
char buf[100];
|
|
struct apu_softc *sc = device_get_softc(dev);
|
|
device_t smb;
|
|
|
|
/* Make sure we do not claim some ISA PNP device. */
|
|
if (isa_get_logicalid(dev) != 0)
|
|
return (ENXIO);
|
|
|
|
sc->sc_model = hw_is_apu();
|
|
if (sc->sc_model == 0)
|
|
return (ENXIO);
|
|
|
|
smb = pci_find_bsf(0, 20, 0);
|
|
if (smb == NULL)
|
|
return (ENXIO);
|
|
|
|
sc->sc_devid = pci_get_devid(smb);
|
|
|
|
snprintf(buf, sizeof(buf), "APU%d", sc->sc_model);
|
|
device_set_desc_copy(dev, buf );
|
|
|
|
switch( sc->sc_devid ) {
|
|
case AMDSB_SMBUS_DEVID:
|
|
error = apu_probe_sb(dev, sc);
|
|
if (error)
|
|
return error;
|
|
break;
|
|
case AMDFCH_SMBUS_DEVID:
|
|
error = apu_probe_fch(dev, sc);
|
|
if (error)
|
|
return error;
|
|
break;
|
|
default: /* Should never reach here. */
|
|
device_printf(dev, "Unexpected APU south bridge\n" );
|
|
return (ENXIO);
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
apuled_attach(device_t dev)
|
|
{
|
|
struct apu_softc *sc = device_get_softc(dev);
|
|
int i;
|
|
int j;
|
|
|
|
for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) {
|
|
sc->sc_rid[i].res = NULL;
|
|
sc->sc_rid[i].rid_type = SYS_RES_MEMORY;
|
|
sc->sc_rid[i].rid = i;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) {
|
|
sc->sc_rid[i].res = bus_alloc_resource_any( dev,
|
|
sc->sc_rid[i].rid_type, &sc->sc_rid[i].rid,
|
|
RF_ACTIVE | RF_SHAREABLE);
|
|
if (sc->sc_rid[i].res == NULL) {
|
|
device_printf( dev, "Unable to allocate memory region %d\n", i );
|
|
for (j = 0; j < i; j++) {
|
|
bus_release_resource(dev, sc->sc_rid[j].rid_type,
|
|
sc->sc_rid[j].rid, sc->sc_rid[j].res);
|
|
sc->sc_rid[j].res = NULL;
|
|
bus_delete_resource(dev, sc->sc_rid[i].rid_type,
|
|
sc->sc_rid[i].rid );
|
|
}
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
|
|
if (sc->sc_devid == AMDFCH_SMBUS_DEVID)
|
|
fch_gpio_dir_set( sc->sc_rid[IDX_RID_MODESW].res, 0, FALSE );
|
|
|
|
for (i = 0; i < sizeof(sc->sc_sw)/sizeof(sc->sc_sw[0]); i++)
|
|
sc->sc_sw[i].cdev = NULL;
|
|
|
|
sc->sc_sw[IDX_SW_MODE].cdev = make_dev(&modesw_cdev, 0, UID_ROOT,
|
|
GID_WHEEL, 0440, "modesw");
|
|
if (sc->sc_sw[IDX_SW_MODE].cdev == NULL) {
|
|
device_printf( dev, "Unable to make modesw\n" );
|
|
} else {
|
|
sc->sc_sw[IDX_SW_MODE].cdev->si_drv1 = &sc->sc_sw[IDX_SW_MODE];
|
|
sc->sc_sw[IDX_SW_MODE].res = sc->sc_rid[IDX_RID_MODESW].res;
|
|
sc->sc_sw[IDX_SW_MODE].offset = 0;
|
|
sc->sc_sw[IDX_SW_MODE].devid = sc->sc_devid;
|
|
}
|
|
|
|
if (sc->sc_model == 3) {
|
|
sc->sc_sw[IDX_SW_SIM].cdev = make_dev(&simsw_cdev, 0, UID_ROOT,
|
|
GID_WHEEL, 0440, "simsw");
|
|
if (sc->sc_sw[IDX_SW_SIM].cdev == NULL) {
|
|
device_printf( dev, "Unable to make simsw\n" );
|
|
} else {
|
|
sc->sc_sw[IDX_SW_SIM].cdev->si_drv1 = &sc->sc_sw[IDX_SW_SIM];
|
|
sc->sc_sw[IDX_SW_SIM].res = sc->sc_rid[IDX_RID_MODESW].res;
|
|
sc->sc_sw[IDX_SW_SIM].offset = sizeof(uint32_t);
|
|
sc->sc_sw[IDX_SW_SIM].devid = sc->sc_devid;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < sizeof(sc->sc_led)/sizeof(sc->sc_led[0]); i++) {
|
|
char name[30];
|
|
|
|
snprintf( name, sizeof(name), "led%d", i + 1 );
|
|
|
|
sc->sc_led[i].res = sc->sc_rid[IDX_RID_LED].res;
|
|
sc->sc_led[i].devid = sc->sc_devid;
|
|
|
|
switch (sc->sc_devid) {
|
|
case AMDSB_SMBUS_DEVID:
|
|
sc->sc_led[i].offset = i;
|
|
break;
|
|
case AMDFCH_SMBUS_DEVID:
|
|
sc->sc_led[i].offset = i * sizeof(uint32_t);
|
|
fch_gpio_dir_set(sc->sc_led[i].res,
|
|
sc->sc_led[i].offset, TRUE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Make sure power LED stays on by default */
|
|
sc->sc_led[i].cdev = led_create_state(apu_led_callback,
|
|
&sc->sc_led[i], name, i == 0);
|
|
|
|
if (sc->sc_led[i].cdev == NULL)
|
|
device_printf(dev, "%s creation failed\n", name);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
apuled_detach(device_t dev)
|
|
{
|
|
struct apu_softc *sc = device_get_softc(dev);
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(sc->sc_led)/sizeof(sc->sc_led[0]); i++)
|
|
if (sc->sc_led[i].cdev != NULL) {
|
|
/* Restore LEDs to stating state */
|
|
if (i == 0)
|
|
apu_led_callback(&sc->sc_led[i], TRUE);
|
|
else
|
|
apu_led_callback(&sc->sc_led[i], FALSE);
|
|
|
|
led_destroy(sc->sc_led[i].cdev);
|
|
}
|
|
|
|
for (i = 0; i < sizeof(sc->sc_sw)/sizeof(sc->sc_sw[0]); i++)
|
|
if (sc->sc_sw[i].cdev != NULL)
|
|
destroy_dev(sc->sc_sw[i].cdev);
|
|
|
|
for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) {
|
|
if (sc->sc_rid[i].res != NULL) {
|
|
bus_release_resource(dev, sc->sc_rid[i].rid_type,
|
|
sc->sc_rid[i].rid, sc->sc_rid[i].res);
|
|
bus_delete_resource(dev, sc->sc_rid[i].rid_type,
|
|
sc->sc_rid[i].rid );
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sw_open(struct cdev *dev __unused, int flags __unused, int fmt __unused,
|
|
struct thread *td)
|
|
{
|
|
int error;
|
|
|
|
error = priv_check(td, PRIV_IO);
|
|
if (error != 0)
|
|
return (error);
|
|
error = securelevel_gt(td->td_ucred, 0);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
sw_read(struct cdev *dev, struct uio *uio, int ioflag) {
|
|
struct apu_cdev *sw = (struct apu_cdev *)dev->si_drv1;
|
|
char ch = '0';
|
|
int error;
|
|
|
|
mtx_lock_spin(&gpio_lock);
|
|
|
|
switch (sw->devid) {
|
|
case AMDSB_SMBUS_DEVID:
|
|
ch = sb_gpio_read( sw->res, sw->offset );
|
|
break;
|
|
case AMDFCH_SMBUS_DEVID:
|
|
fch_gpio_dir_set( sw->res, sw->offset, FALSE );
|
|
ch = fch_gpio_read( sw->res, sw->offset );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mtx_unlock_spin(&gpio_lock);
|
|
|
|
error = uiomove(&ch, sizeof(ch), uio);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
sw_write(struct cdev *dev, struct uio *uio, int ioflag) {
|
|
struct apu_cdev *sw = (struct apu_cdev *)dev->si_drv1;
|
|
char ch;
|
|
int error;
|
|
|
|
error = uiomove(&ch, sizeof(ch), uio);
|
|
if (error)
|
|
return (error);
|
|
|
|
mtx_lock_spin(&gpio_lock);
|
|
|
|
switch (sw->devid) {
|
|
case AMDSB_SMBUS_DEVID:
|
|
break;
|
|
case AMDFCH_SMBUS_DEVID:
|
|
fch_gpio_dir_set( sw->res, sw->offset, TRUE );
|
|
fch_gpio_write(sw->res, sw->offset, ch);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mtx_unlock_spin(&gpio_lock);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sw_close(struct cdev *dev __unused, int flags __unused, int fmt __unused,
|
|
struct thread *td __unused)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
apu_led_callback(void *ptr, int onoff)
|
|
{
|
|
struct apu_cdev *led = (struct apu_cdev *)ptr;
|
|
|
|
mtx_lock_spin(&gpio_lock);
|
|
|
|
switch (led->devid) {
|
|
case AMDSB_SMBUS_DEVID:
|
|
sb_gpio_write( led->res, led->offset, onoff );
|
|
break;
|
|
case AMDFCH_SMBUS_DEVID:
|
|
fch_gpio_write( led->res, led->offset, onoff );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mtx_unlock_spin(&gpio_lock);
|
|
}
|
|
|
|
static device_method_t apuled_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, apuled_probe),
|
|
DEVMETHOD(device_attach, apuled_attach),
|
|
DEVMETHOD(device_detach, apuled_detach),
|
|
DEVMETHOD(device_identify, apuled_identify),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t apuled_driver = {
|
|
"apuled",
|
|
apuled_methods,
|
|
sizeof(struct apu_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(apuled, isa, apuled_driver, 0, 0);
|