intelspi: add PCI attachment (Lynx/Wildcat/Sunrise Point)
Also adds fixups and cleanups: - apply the child's mode/speed - implement suspend/resume support - use RF_SHAREABLE interrupts - use bus_delayed_attach_children since the transfer can use interrupts - add support for newly added spibus features (cs_delay and flags) Operation tested on Broadwell (Wildcat Point) MacBookPro12,1. Attachment also tested on Kaby Lake (Sunrise Point) Pixelbook. Reviewed by: wulf MFC after: 1 month Differential revision: https://reviews.freebsd.org/D29249
This commit is contained in:
parent
3c08673438
commit
1f40866feb
@ -145,6 +145,8 @@ dev/imcsmb/imcsmb.c optional imcsmb
|
|||||||
dev/imcsmb/imcsmb_pci.c optional imcsmb pci
|
dev/imcsmb/imcsmb_pci.c optional imcsmb pci
|
||||||
dev/intel/pchtherm.c optional pchtherm
|
dev/intel/pchtherm.c optional pchtherm
|
||||||
dev/intel/spi.c optional intelspi
|
dev/intel/spi.c optional intelspi
|
||||||
|
dev/intel/spi_pci.c optional intelspi pci
|
||||||
|
dev/intel/spi_acpi.c optional intelspi acpi
|
||||||
dev/io/iodev.c optional io
|
dev/io/iodev.c optional io
|
||||||
dev/iommu/busdma_iommu.c optional acpi iommu pci
|
dev/iommu/busdma_iommu.c optional acpi iommu pci
|
||||||
dev/iommu/iommu_gas.c optional acpi iommu pci
|
dev/iommu/iommu_gas.c optional acpi iommu pci
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
/*-
|
/*-
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*
|
||||||
* Copyright (c) 2016 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
|
* Copyright (c) 2016 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -24,9 +26,6 @@
|
|||||||
* SUCH DAMAGE.
|
* SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sys/cdefs.h>
|
|
||||||
__FBSDID("$FreeBSD$");
|
|
||||||
|
|
||||||
#include "opt_acpi.h"
|
#include "opt_acpi.h"
|
||||||
|
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
@ -42,12 +41,7 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <dev/spibus/spi.h>
|
#include <dev/spibus/spi.h>
|
||||||
#include <dev/spibus/spibusvar.h>
|
#include <dev/spibus/spibusvar.h>
|
||||||
|
|
||||||
#include <contrib/dev/acpica/include/acpi.h>
|
#include <dev/intel/spi.h>
|
||||||
#include <contrib/dev/acpica/include/accommon.h>
|
|
||||||
|
|
||||||
#include <dev/acpica/acpivar.h>
|
|
||||||
|
|
||||||
#include "spibus_if.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Macros for driver mutex locking
|
* Macros for driver mutex locking
|
||||||
@ -71,12 +65,13 @@ __FBSDID("$FreeBSD$");
|
|||||||
#define RX_FIFO_THRESHOLD 2
|
#define RX_FIFO_THRESHOLD 2
|
||||||
#define CLOCK_DIV_10MHZ 5
|
#define CLOCK_DIV_10MHZ 5
|
||||||
#define DATA_SIZE_8BITS 8
|
#define DATA_SIZE_8BITS 8
|
||||||
|
#define MAX_CLOCK_RATE 50000000
|
||||||
|
|
||||||
#define CS_LOW 0
|
#define CS_LOW 0
|
||||||
#define CS_HIGH 1
|
#define CS_HIGH 1
|
||||||
|
|
||||||
#define INTELSPI_SSPREG_SSCR0 0x0
|
#define INTELSPI_SSPREG_SSCR0 0x0
|
||||||
#define SSCR0_SCR(n) (((n) - 1) << 8)
|
#define SSCR0_SCR(n) ((((n) - 1) & 0xfff) << 8)
|
||||||
#define SSCR0_SSE (1 << 7)
|
#define SSCR0_SSE (1 << 7)
|
||||||
#define SSCR0_FRF_SPI (0 << 4)
|
#define SSCR0_FRF_SPI (0 << 4)
|
||||||
#define SSCR0_DSS(n) (((n) - 1) << 0)
|
#define SSCR0_DSS(n) (((n) - 1) << 0)
|
||||||
@ -88,10 +83,6 @@ __FBSDID("$FreeBSD$");
|
|||||||
#define SSCR1_SPI_SPH (1 << 4)
|
#define SSCR1_SPI_SPH (1 << 4)
|
||||||
#define SSCR1_SPI_SPO (1 << 3)
|
#define SSCR1_SPI_SPO (1 << 3)
|
||||||
#define SSCR1_MODE_MASK (SSCR1_SPI_SPO | SSCR1_SPI_SPH)
|
#define SSCR1_MODE_MASK (SSCR1_SPI_SPO | SSCR1_SPI_SPH)
|
||||||
#define SSCR1_MODE_0 (0)
|
|
||||||
#define SSCR1_MODE_1 (SSCR1_SPI_SPH)
|
|
||||||
#define SSCR1_MODE_2 (SSCR1_SPI_SPO)
|
|
||||||
#define SSCR1_MODE_3 (SSCR1_SPI_SPO | SSCR1_SPI_SPH)
|
|
||||||
#define SSCR1_TIE (1 << 1)
|
#define SSCR1_TIE (1 << 1)
|
||||||
#define SSCR1_RIE (1 << 0)
|
#define SSCR1_RIE (1 << 0)
|
||||||
#define INTELSPI_SSPREG_SSSR 0x8
|
#define INTELSPI_SSPREG_SSSR 0x8
|
||||||
@ -110,36 +101,14 @@ __FBSDID("$FreeBSD$");
|
|||||||
#define INTELSPI_SSPREG_ITF 0x40
|
#define INTELSPI_SSPREG_ITF 0x40
|
||||||
#define INTELSPI_SSPREG_SITF 0x44
|
#define INTELSPI_SSPREG_SITF 0x44
|
||||||
#define INTELSPI_SSPREG_SIRF 0x48
|
#define INTELSPI_SSPREG_SIRF 0x48
|
||||||
#define INTELSPI_SSPREG_PRV_CLOCK_PARAMS 0x400
|
#define SPI_CS_CTRL(sc) \
|
||||||
#define INTELSPI_SSPREG_RESETS 0x404
|
(intelspi_infos[sc->sc_vers].reg_lpss_base + \
|
||||||
#define INTELSPI_SSPREG_GENERAL 0x408
|
intelspi_infos[sc->sc_vers].reg_cs_ctrl)
|
||||||
#define INTELSPI_SSPREG_SSP_REG 0x40C
|
|
||||||
#define INTELSPI_SSPREG_SPI_CS_CTRL 0x418
|
|
||||||
#define SPI_CS_CTRL_CS_MASK (3)
|
#define SPI_CS_CTRL_CS_MASK (3)
|
||||||
#define SPI_CS_CTRL_SW_MODE (1 << 0)
|
#define SPI_CS_CTRL_SW_MODE (1 << 0)
|
||||||
#define SPI_CS_CTRL_HW_MODE (1 << 0)
|
#define SPI_CS_CTRL_HW_MODE (1 << 0)
|
||||||
#define SPI_CS_CTRL_CS_HIGH (1 << 1)
|
#define SPI_CS_CTRL_CS_HIGH (1 << 1)
|
||||||
#define SPI_CS_CTRL_CS_LOW (0 << 1)
|
|
||||||
|
|
||||||
struct intelspi_softc {
|
|
||||||
ACPI_HANDLE sc_handle;
|
|
||||||
device_t sc_dev;
|
|
||||||
struct mtx sc_mtx;
|
|
||||||
int sc_mem_rid;
|
|
||||||
struct resource *sc_mem_res;
|
|
||||||
int sc_irq_rid;
|
|
||||||
struct resource *sc_irq_res;
|
|
||||||
void *sc_irq_ih;
|
|
||||||
struct spi_command *sc_cmd;
|
|
||||||
uint32_t sc_len;
|
|
||||||
uint32_t sc_read;
|
|
||||||
uint32_t sc_flags;
|
|
||||||
uint32_t sc_written;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int intelspi_probe(device_t dev);
|
|
||||||
static int intelspi_attach(device_t dev);
|
|
||||||
static int intelspi_detach(device_t dev);
|
|
||||||
static void intelspi_intr(void *);
|
static void intelspi_intr(void *);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -294,25 +263,15 @@ intelspi_init(struct intelspi_softc *sc)
|
|||||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0);
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0);
|
||||||
|
|
||||||
/* Manual CS control */
|
/* Manual CS control */
|
||||||
reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL);
|
reg = INTELSPI_READ(sc, SPI_CS_CTRL(sc));
|
||||||
reg &= ~(SPI_CS_CTRL_CS_MASK);
|
reg &= ~(SPI_CS_CTRL_CS_MASK);
|
||||||
reg |= (SPI_CS_CTRL_SW_MODE | SPI_CS_CTRL_CS_HIGH);
|
reg |= (SPI_CS_CTRL_SW_MODE | SPI_CS_CTRL_CS_HIGH);
|
||||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg);
|
INTELSPI_WRITE(sc, SPI_CS_CTRL(sc), reg);
|
||||||
|
|
||||||
/* Set TX/RX FIFO IRQ threshold levels */
|
/* Set TX/RX FIFO IRQ threshold levels */
|
||||||
reg = SSCR1_TFT(TX_FIFO_THRESHOLD) | SSCR1_RFT(RX_FIFO_THRESHOLD);
|
reg = SSCR1_TFT(TX_FIFO_THRESHOLD) | SSCR1_RFT(RX_FIFO_THRESHOLD);
|
||||||
/*
|
|
||||||
* Set SPI mode. This should be part of transaction or sysctl
|
|
||||||
*/
|
|
||||||
reg |= SSCR1_MODE_0;
|
|
||||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg);
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg);
|
||||||
|
|
||||||
/*
|
|
||||||
* Parent clock on Minowboard Turbot is 50MHz
|
|
||||||
* divide it by 5 to set to more or less reasonable
|
|
||||||
* value. But this should be part of transaction config
|
|
||||||
* or sysctl
|
|
||||||
*/
|
|
||||||
reg = SSCR0_SCR(CLOCK_DIV_10MHZ);
|
reg = SSCR0_SCR(CLOCK_DIV_10MHZ);
|
||||||
/* Put SSP in SPI mode */
|
/* Put SSP in SPI mode */
|
||||||
reg |= SSCR0_FRF_SPI;
|
reg |= SSCR0_FRF_SPI;
|
||||||
@ -328,24 +287,23 @@ intelspi_set_cs(struct intelspi_softc *sc, int level)
|
|||||||
{
|
{
|
||||||
uint32_t reg;
|
uint32_t reg;
|
||||||
|
|
||||||
reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL);
|
reg = INTELSPI_READ(sc, SPI_CS_CTRL(sc));
|
||||||
reg &= ~(SPI_CS_CTRL_CS_MASK);
|
reg &= ~(SPI_CS_CTRL_CS_MASK);
|
||||||
reg |= SPI_CS_CTRL_SW_MODE;
|
reg |= SPI_CS_CTRL_SW_MODE;
|
||||||
|
|
||||||
if (level == CS_HIGH)
|
if (level == CS_HIGH)
|
||||||
reg |= SPI_CS_CTRL_CS_HIGH;
|
reg |= SPI_CS_CTRL_CS_HIGH;
|
||||||
else
|
|
||||||
reg |= SPI_CS_CTRL_CS_LOW;
|
|
||||||
|
|
||||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg);
|
INTELSPI_WRITE(sc, SPI_CS_CTRL(sc), reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
||||||
{
|
{
|
||||||
struct intelspi_softc *sc;
|
struct intelspi_softc *sc;
|
||||||
int err;
|
int err, poll_limit;
|
||||||
uint32_t sscr1;
|
uint32_t sscr0, sscr1, mode, clock, cs_delay;
|
||||||
|
bool restart = false;
|
||||||
|
|
||||||
sc = device_get_softc(dev);
|
sc = device_get_softc(dev);
|
||||||
err = 0;
|
err = 0;
|
||||||
@ -359,6 +317,8 @@ intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
|||||||
|
|
||||||
/* If the controller is in use wait until it is available. */
|
/* If the controller is in use wait until it is available. */
|
||||||
while (sc->sc_flags & INTELSPI_BUSY) {
|
while (sc->sc_flags & INTELSPI_BUSY) {
|
||||||
|
if ((cmd->flags & SPI_FLAG_NO_SLEEP) == SPI_FLAG_NO_SLEEP)
|
||||||
|
return (EBUSY);
|
||||||
err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", 0);
|
err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", 0);
|
||||||
if (err == EINTR) {
|
if (err == EINTR) {
|
||||||
INTELSPI_UNLOCK(sc);
|
INTELSPI_UNLOCK(sc);
|
||||||
@ -369,6 +329,45 @@ intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
|||||||
/* Now we have control over SPI controller. */
|
/* Now we have control over SPI controller. */
|
||||||
sc->sc_flags = INTELSPI_BUSY;
|
sc->sc_flags = INTELSPI_BUSY;
|
||||||
|
|
||||||
|
/* Configure the clock rate and SPI mode. */
|
||||||
|
spibus_get_clock(child, &clock);
|
||||||
|
spibus_get_mode(child, &mode);
|
||||||
|
|
||||||
|
if (clock != sc->sc_clock || mode != sc->sc_mode) {
|
||||||
|
sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0);
|
||||||
|
sscr0 &= ~SSCR0_SSE;
|
||||||
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0);
|
||||||
|
restart = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clock != sc->sc_clock) {
|
||||||
|
sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0);
|
||||||
|
sscr0 &= ~SSCR0_SCR(0xfff);
|
||||||
|
if (clock == 0)
|
||||||
|
sscr0 |= SSCR0_SCR(CLOCK_DIV_10MHZ);
|
||||||
|
else
|
||||||
|
sscr0 |= SSCR0_SCR(howmany(MAX_CLOCK_RATE, min(MAX_CLOCK_RATE, clock)));
|
||||||
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0);
|
||||||
|
sc->sc_clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode != sc->sc_mode) {
|
||||||
|
sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1);
|
||||||
|
sscr1 &= ~SSCR1_MODE_MASK;
|
||||||
|
if (mode & SPIBUS_MODE_CPHA)
|
||||||
|
sscr1 |= SSCR1_SPI_SPH;
|
||||||
|
if (mode & SPIBUS_MODE_CPOL)
|
||||||
|
sscr1 |= SSCR1_SPI_SPO;
|
||||||
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1);
|
||||||
|
sc->sc_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restart) {
|
||||||
|
sscr0 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR0);
|
||||||
|
sscr0 |= SSCR0_SSE;
|
||||||
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, sscr0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Save a pointer to the SPI command. */
|
/* Save a pointer to the SPI command. */
|
||||||
sc->sc_cmd = cmd;
|
sc->sc_cmd = cmd;
|
||||||
sc->sc_read = 0;
|
sc->sc_read = 0;
|
||||||
@ -377,19 +376,36 @@ intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
|||||||
|
|
||||||
/* Enable CS */
|
/* Enable CS */
|
||||||
intelspi_set_cs(sc, CS_LOW);
|
intelspi_set_cs(sc, CS_LOW);
|
||||||
/* Transfer as much as possible to FIFOs */
|
|
||||||
if (!intelspi_transact(sc)) {
|
|
||||||
/* If FIFO is not large enough - enable interrupts */
|
|
||||||
sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1);
|
|
||||||
sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
|
|
||||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1);
|
|
||||||
|
|
||||||
/* and wait for transaction to complete */
|
/* Wait the CS delay */
|
||||||
err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2);
|
spibus_get_cs_delay(child, &cs_delay);
|
||||||
|
DELAY(cs_delay);
|
||||||
|
|
||||||
|
/* Transfer as much as possible to FIFOs */
|
||||||
|
if ((cmd->flags & SPI_FLAG_NO_SLEEP) == SPI_FLAG_NO_SLEEP) {
|
||||||
|
/* We cannot wait with mtx_sleep if we're called from e.g. an ithread */
|
||||||
|
poll_limit = 2000;
|
||||||
|
while (!intelspi_transact(sc) && poll_limit-- > 0)
|
||||||
|
DELAY(1000);
|
||||||
|
if (poll_limit == 0) {
|
||||||
|
device_printf(dev, "polling was stuck, transaction not finished\n");
|
||||||
|
err = EIO;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!intelspi_transact(sc)) {
|
||||||
|
/* If FIFO is not large enough - enable interrupts */
|
||||||
|
sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1);
|
||||||
|
sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
|
||||||
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1);
|
||||||
|
|
||||||
|
/* and wait for transaction to complete */
|
||||||
|
err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* de-asser CS */
|
/* De-assert CS */
|
||||||
intelspi_set_cs(sc, CS_HIGH);
|
if ((cmd->flags & SPI_FLAG_KEEP_CS) == 0)
|
||||||
|
intelspi_set_cs(sc, CS_HIGH);
|
||||||
|
|
||||||
/* Clear transaction details */
|
/* Clear transaction details */
|
||||||
sc->sc_cmd = NULL;
|
sc->sc_cmd = NULL;
|
||||||
@ -419,32 +435,16 @@ intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd)
|
|||||||
return (err);
|
return (err);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
intelspi_probe(device_t dev)
|
|
||||||
{
|
|
||||||
static char *gpio_ids[] = { "80860F0E", NULL };
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
if (acpi_disabled("spi") )
|
|
||||||
return (ENXIO);
|
|
||||||
rv = ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids, NULL);
|
|
||||||
if (rv <= 0)
|
|
||||||
device_set_desc(dev, "Intel SPI Controller");
|
|
||||||
return (rv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
intelspi_attach(device_t dev)
|
intelspi_attach(device_t dev)
|
||||||
{
|
{
|
||||||
struct intelspi_softc *sc;
|
struct intelspi_softc *sc;
|
||||||
|
|
||||||
sc = device_get_softc(dev);
|
sc = device_get_softc(dev);
|
||||||
sc->sc_dev = dev;
|
sc->sc_dev = dev;
|
||||||
sc->sc_handle = acpi_get_handle(dev);
|
|
||||||
|
|
||||||
INTELSPI_LOCK_INIT(sc);
|
INTELSPI_LOCK_INIT(sc);
|
||||||
|
|
||||||
sc->sc_mem_rid = 0;
|
|
||||||
sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev,
|
sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev,
|
||||||
SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE);
|
SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE);
|
||||||
if (sc->sc_mem_res == NULL) {
|
if (sc->sc_mem_res == NULL) {
|
||||||
@ -452,9 +452,8 @@ intelspi_attach(device_t dev)
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc->sc_irq_rid = 0;
|
|
||||||
sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev,
|
sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev,
|
||||||
SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE);
|
SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE | RF_SHAREABLE);
|
||||||
if (sc->sc_irq_res == NULL) {
|
if (sc->sc_irq_res == NULL) {
|
||||||
device_printf(dev, "can't allocate IRQ resource\n");
|
device_printf(dev, "can't allocate IRQ resource\n");
|
||||||
goto error;
|
goto error;
|
||||||
@ -471,7 +470,7 @@ intelspi_attach(device_t dev)
|
|||||||
|
|
||||||
device_add_child(dev, "spibus", -1);
|
device_add_child(dev, "spibus", -1);
|
||||||
|
|
||||||
return (bus_generic_attach(dev));
|
return (bus_delayed_attach_children(dev));
|
||||||
|
|
||||||
error:
|
error:
|
||||||
INTELSPI_LOCK_DESTROY(sc);
|
INTELSPI_LOCK_DESTROY(sc);
|
||||||
@ -487,7 +486,7 @@ intelspi_attach(device_t dev)
|
|||||||
return (ENXIO);
|
return (ENXIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
intelspi_detach(device_t dev)
|
intelspi_detach(device_t dev)
|
||||||
{
|
{
|
||||||
struct intelspi_softc *sc;
|
struct intelspi_softc *sc;
|
||||||
@ -510,24 +509,50 @@ intelspi_detach(device_t dev)
|
|||||||
return (bus_generic_detach(dev));
|
return (bus_generic_detach(dev));
|
||||||
}
|
}
|
||||||
|
|
||||||
static device_method_t intelspi_methods[] = {
|
int
|
||||||
/* Device interface */
|
intelspi_suspend(device_t dev)
|
||||||
DEVMETHOD(device_probe, intelspi_probe),
|
{
|
||||||
DEVMETHOD(device_attach, intelspi_attach),
|
struct intelspi_softc *sc;
|
||||||
DEVMETHOD(device_detach, intelspi_detach),
|
int err, i;
|
||||||
|
|
||||||
/* SPI interface */
|
sc = device_get_softc(dev);
|
||||||
DEVMETHOD(spibus_transfer, intelspi_transfer),
|
|
||||||
|
|
||||||
DEVMETHOD_END
|
err = bus_generic_suspend(dev);
|
||||||
};
|
if (err)
|
||||||
|
return (err);
|
||||||
|
|
||||||
static driver_t intelspi_driver = {
|
for (i = 0; i < 9; i++) {
|
||||||
"spi",
|
unsigned long offset = i * sizeof(uint32_t);
|
||||||
intelspi_methods,
|
sc->sc_regs[i] = INTELSPI_READ(sc,
|
||||||
sizeof(struct intelspi_softc),
|
intelspi_infos[sc->sc_vers].reg_lpss_base + offset);
|
||||||
};
|
}
|
||||||
|
|
||||||
DRIVER_MODULE(intelspi, acpi, intelspi_driver, 0, 0);
|
/* Shutdown just in case */
|
||||||
MODULE_DEPEND(intelspi, acpi, 1, 1, 1);
|
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0);
|
||||||
MODULE_DEPEND(intelspi, spibus, 1, 1, 1);
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
intelspi_resume(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
sc = device_get_softc(dev);
|
||||||
|
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
unsigned long offset = i * sizeof(uint32_t);
|
||||||
|
INTELSPI_WRITE(sc,
|
||||||
|
intelspi_infos[sc->sc_vers].reg_lpss_base + offset,
|
||||||
|
sc->sc_regs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
intelspi_init(sc);
|
||||||
|
|
||||||
|
/* Ensure the next transfer would reconfigure these */
|
||||||
|
sc->sc_clock = 0;
|
||||||
|
sc->sc_mode = 0;
|
||||||
|
|
||||||
|
return (bus_generic_resume(dev));
|
||||||
|
}
|
||||||
|
99
sys/dev/intel/spi.h
Normal file
99
sys/dev/intel/spi.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*-
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _DEV_INTEL_SPI_H_
|
||||||
|
#define _DEV_INTEL_SPI_H_
|
||||||
|
|
||||||
|
#include <contrib/dev/acpica/include/acpi.h>
|
||||||
|
#include <contrib/dev/acpica/include/accommon.h>
|
||||||
|
|
||||||
|
#include <dev/acpica/acpivar.h>
|
||||||
|
|
||||||
|
enum intelspi_vers {
|
||||||
|
SPI_BAYTRAIL,
|
||||||
|
SPI_BRASWELL,
|
||||||
|
SPI_LYNXPOINT,
|
||||||
|
SPI_SUNRISEPOINT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Same order as intelspi_vers */
|
||||||
|
static const struct intelspi_info {
|
||||||
|
const char *desc;
|
||||||
|
uint32_t reg_lpss_base;
|
||||||
|
uint32_t reg_cs_ctrl;
|
||||||
|
} intelspi_infos[] = {
|
||||||
|
[SPI_BAYTRAIL] = {
|
||||||
|
.desc = "Intel Bay Trail SPI Controller",
|
||||||
|
.reg_lpss_base = 0x400,
|
||||||
|
.reg_cs_ctrl = 0x18,
|
||||||
|
},
|
||||||
|
[SPI_BRASWELL] = {
|
||||||
|
.desc = "Intel Braswell SPI Controller",
|
||||||
|
.reg_lpss_base = 0x400,
|
||||||
|
.reg_cs_ctrl = 0x18,
|
||||||
|
},
|
||||||
|
[SPI_LYNXPOINT] = {
|
||||||
|
.desc = "Intel Lynx Point / Wildcat Point SPI Controller",
|
||||||
|
.reg_lpss_base = 0x800,
|
||||||
|
.reg_cs_ctrl = 0x18,
|
||||||
|
},
|
||||||
|
[SPI_SUNRISEPOINT] = {
|
||||||
|
.desc = "Intel Sunrise Point SPI Controller",
|
||||||
|
.reg_lpss_base = 0x200,
|
||||||
|
.reg_cs_ctrl = 0x24,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct intelspi_softc {
|
||||||
|
ACPI_HANDLE sc_handle;
|
||||||
|
device_t sc_dev;
|
||||||
|
enum intelspi_vers sc_vers;
|
||||||
|
struct mtx sc_mtx;
|
||||||
|
int sc_mem_rid;
|
||||||
|
struct resource *sc_mem_res;
|
||||||
|
int sc_irq_rid;
|
||||||
|
struct resource *sc_irq_res;
|
||||||
|
void *sc_irq_ih;
|
||||||
|
struct spi_command *sc_cmd;
|
||||||
|
uint32_t sc_len;
|
||||||
|
uint32_t sc_read;
|
||||||
|
uint32_t sc_flags;
|
||||||
|
uint32_t sc_written;
|
||||||
|
uint32_t sc_clock;
|
||||||
|
uint32_t sc_mode;
|
||||||
|
/* LPSS private register storage for suspend-resume */
|
||||||
|
uint32_t sc_regs[9];
|
||||||
|
};
|
||||||
|
|
||||||
|
int intelspi_attach(device_t dev);
|
||||||
|
int intelspi_detach(device_t dev);
|
||||||
|
int intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd);
|
||||||
|
int intelspi_suspend(device_t dev);
|
||||||
|
int intelspi_resume(device_t dev);
|
||||||
|
|
||||||
|
#endif
|
111
sys/dev/intel/spi_acpi.c
Normal file
111
sys/dev/intel/spi_acpi.c
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*-
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Val Packett <val@packett.cool>
|
||||||
|
*
|
||||||
|
* 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 "opt_acpi.h"
|
||||||
|
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/bus.h>
|
||||||
|
#include <sys/kernel.h>
|
||||||
|
#include <sys/module.h>
|
||||||
|
#include <sys/proc.h>
|
||||||
|
#include <sys/rman.h>
|
||||||
|
|
||||||
|
#include <dev/intel/spi.h>
|
||||||
|
|
||||||
|
#include "spibus_if.h"
|
||||||
|
|
||||||
|
static const struct intelspi_acpi_device {
|
||||||
|
const char *hid;
|
||||||
|
enum intelspi_vers vers;
|
||||||
|
} intelspi_acpi_devices[] = {
|
||||||
|
{ "80860F0E", SPI_BAYTRAIL },
|
||||||
|
{ "8086228E", SPI_BRASWELL },
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *intelspi_ids[] = { "80860F0E", "8086228E", NULL };
|
||||||
|
|
||||||
|
static int
|
||||||
|
intelspi_acpi_probe(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc = device_get_softc(dev);
|
||||||
|
char *hid;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (acpi_disabled("spi"))
|
||||||
|
return (ENXIO);
|
||||||
|
|
||||||
|
if (ACPI_ID_PROBE(device_get_parent(dev), dev, intelspi_ids, &hid) > 0)
|
||||||
|
return (ENXIO);
|
||||||
|
|
||||||
|
for (i = 0; i < nitems(intelspi_acpi_devices); i++) {
|
||||||
|
if (strcmp(intelspi_acpi_devices[i].hid, hid) == 0) {
|
||||||
|
sc->sc_vers = intelspi_acpi_devices[i].vers;
|
||||||
|
sc->sc_handle = acpi_get_handle(dev);
|
||||||
|
device_set_desc(dev, intelspi_infos[sc->sc_vers].desc);
|
||||||
|
return (BUS_PROBE_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ENXIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
intelspi_acpi_attach(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc = device_get_softc(dev);
|
||||||
|
|
||||||
|
sc->sc_mem_rid = 0;
|
||||||
|
sc->sc_irq_rid = 0;
|
||||||
|
|
||||||
|
return (intelspi_attach(dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
static device_method_t intelspi_acpi_methods[] = {
|
||||||
|
/* Device interface */
|
||||||
|
DEVMETHOD(device_probe, intelspi_acpi_probe),
|
||||||
|
DEVMETHOD(device_attach, intelspi_acpi_attach),
|
||||||
|
DEVMETHOD(device_detach, intelspi_detach),
|
||||||
|
DEVMETHOD(device_suspend, intelspi_suspend),
|
||||||
|
DEVMETHOD(device_resume, intelspi_resume),
|
||||||
|
|
||||||
|
/* SPI interface */
|
||||||
|
DEVMETHOD(spibus_transfer, intelspi_transfer),
|
||||||
|
|
||||||
|
DEVMETHOD_END
|
||||||
|
};
|
||||||
|
|
||||||
|
static driver_t intelspi_acpi_driver = {
|
||||||
|
"spi",
|
||||||
|
intelspi_acpi_methods,
|
||||||
|
sizeof(struct intelspi_softc),
|
||||||
|
};
|
||||||
|
|
||||||
|
DRIVER_MODULE(intelspi, acpi, intelspi_acpi_driver, 0, 0);
|
||||||
|
MODULE_DEPEND(intelspi, acpi, 1, 1, 1);
|
||||||
|
MODULE_DEPEND(intelspi, spibus, 1, 1, 1);
|
||||||
|
ACPI_PNP_INFO(intelspi_ids);
|
138
sys/dev/intel/spi_pci.c
Normal file
138
sys/dev/intel/spi_pci.c
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*-
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Val Packett <val@packett.cool>
|
||||||
|
*
|
||||||
|
* 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 "opt_acpi.h"
|
||||||
|
#include "opt_pci.h"
|
||||||
|
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/bus.h>
|
||||||
|
#include <sys/kernel.h>
|
||||||
|
#include <sys/module.h>
|
||||||
|
#include <sys/proc.h>
|
||||||
|
#include <sys/rman.h>
|
||||||
|
|
||||||
|
#include <dev/intel/spi.h>
|
||||||
|
#include <dev/pci/pcireg.h>
|
||||||
|
#include <dev/pci/pcivar.h>
|
||||||
|
|
||||||
|
#include "spibus_if.h"
|
||||||
|
|
||||||
|
static struct intelspi_pci_device {
|
||||||
|
uint32_t devid;
|
||||||
|
enum intelspi_vers vers;
|
||||||
|
} intelspi_pci_devices[] = {
|
||||||
|
{ 0x9c658086, SPI_LYNXPOINT },
|
||||||
|
{ 0x9c668086, SPI_LYNXPOINT },
|
||||||
|
{ 0x9ce58086, SPI_LYNXPOINT },
|
||||||
|
{ 0x9ce68086, SPI_LYNXPOINT },
|
||||||
|
{ 0x9d298086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0x9d2a8086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa1298086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa12a8086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa2a98086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa2aa8086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa3a98086, SPI_SUNRISEPOINT },
|
||||||
|
{ 0xa3aa8086, SPI_SUNRISEPOINT },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
intelspi_pci_probe(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc = device_get_softc(dev);
|
||||||
|
uint32_t devid = pci_get_devid(dev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < nitems(intelspi_pci_devices); i++) {
|
||||||
|
if (intelspi_pci_devices[i].devid == devid) {
|
||||||
|
sc->sc_vers = intelspi_pci_devices[i].vers;
|
||||||
|
/* The PCI device is listed in ACPI too.
|
||||||
|
* Not that we use the handle for anything... */
|
||||||
|
sc->sc_handle = acpi_get_handle(dev);
|
||||||
|
device_set_desc(dev, intelspi_infos[sc->sc_vers].desc);
|
||||||
|
return (BUS_PROBE_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ENXIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
intelspi_pci_attach(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc = device_get_softc(dev);
|
||||||
|
|
||||||
|
sc->sc_mem_rid = PCIR_BAR(0);
|
||||||
|
sc->sc_irq_rid = 0;
|
||||||
|
if (pci_alloc_msi(dev, &sc->sc_irq_rid)) {
|
||||||
|
device_printf(dev, "Using MSI\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (intelspi_attach(dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
intelspi_pci_detach(device_t dev)
|
||||||
|
{
|
||||||
|
struct intelspi_softc *sc = device_get_softc(dev);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = intelspi_detach(dev);
|
||||||
|
if (err)
|
||||||
|
return (err);
|
||||||
|
|
||||||
|
if (sc->sc_irq_rid != 0)
|
||||||
|
pci_release_msi(dev);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static device_method_t intelspi_pci_methods[] = {
|
||||||
|
/* Device interface */
|
||||||
|
DEVMETHOD(device_probe, intelspi_pci_probe),
|
||||||
|
DEVMETHOD(device_attach, intelspi_pci_attach),
|
||||||
|
DEVMETHOD(device_detach, intelspi_pci_detach),
|
||||||
|
DEVMETHOD(device_suspend, intelspi_suspend),
|
||||||
|
DEVMETHOD(device_resume, intelspi_resume),
|
||||||
|
|
||||||
|
/* SPI interface */
|
||||||
|
DEVMETHOD(spibus_transfer, intelspi_transfer),
|
||||||
|
|
||||||
|
DEVMETHOD_END
|
||||||
|
};
|
||||||
|
|
||||||
|
static driver_t intelspi_pci_driver = {
|
||||||
|
"spi",
|
||||||
|
intelspi_pci_methods,
|
||||||
|
sizeof(struct intelspi_softc),
|
||||||
|
};
|
||||||
|
|
||||||
|
DRIVER_MODULE(intelspi, pci, intelspi_pci_driver, 0, 0);
|
||||||
|
MODULE_DEPEND(intelspi, pci, 1, 1, 1);
|
||||||
|
MODULE_DEPEND(intelspi, spibus, 1, 1, 1);
|
||||||
|
MODULE_PNP_INFO("W32:vendor/device", pci, intelspi, intelspi_pci_devices,
|
||||||
|
nitems(intelspi_pci_devices));
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.PATH: ${SRCTOP}/sys/dev/intel
|
.PATH: ${SRCTOP}/sys/dev/intel
|
||||||
KMOD= intelspi
|
KMOD= intelspi
|
||||||
SRCS= spi.c
|
SRCS= spi.c spi_acpi.c spi_pci.c
|
||||||
SRCS+= acpi_if.h device_if.h bus_if.h opt_acpi.h spibus_if.h
|
SRCS+= acpi_if.h pci_if.h device_if.h bus_if.h opt_acpi.h opt_pci.h spibus_if.h
|
||||||
|
|
||||||
.include <bsd.kmod.mk>
|
.include <bsd.kmod.mk>
|
||||||
|
Loading…
Reference in New Issue
Block a user