Changeset View
Standalone View
sys/dev/intel/spi.c
/*- | /*- | ||||
* 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. | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer. | * notice, this list of conditions and the following disclaimer. | ||||
Show All 27 Lines | |||||
#include <sys/rman.h> | #include <sys/rman.h> | ||||
#include <machine/bus.h> | #include <machine/bus.h> | ||||
#include <machine/resource.h> | #include <machine/resource.h> | ||||
#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 | ||||
*/ | */ | ||||
#define INTELSPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) | #define INTELSPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) | ||||
#define INTELSPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) | #define INTELSPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) | ||||
#define INTELSPI_LOCK_INIT(_sc) \ | #define INTELSPI_LOCK_INIT(_sc) \ | ||||
mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ | mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ | ||||
"intelspi", MTX_DEF) | "intelspi", MTX_DEF) | ||||
#define INTELSPI_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) | #define INTELSPI_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) | ||||
#define INTELSPI_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) | #define INTELSPI_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) | ||||
#define INTELSPI_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) | #define INTELSPI_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) | ||||
#define INTELSPI_WRITE(_sc, _off, _val) \ | #define INTELSPI_WRITE(_sc, _off, _val) \ | ||||
bus_write_4((_sc)->sc_mem_res, (_off), (_val)) | bus_write_4((_sc)->sc_mem_res, (_off), (_val)) | ||||
#define INTELSPI_READ(_sc, _off) \ | #define INTELSPI_READ(_sc, _off) \ | ||||
bus_read_4((_sc)->sc_mem_res, (_off)) | bus_read_4((_sc)->sc_mem_res, (_off)) | ||||
#define INTELSPI_BUSY 0x1 | #define INTELSPI_BUSY 0x1 | ||||
#define TX_FIFO_THRESHOLD 2 | #define TX_FIFO_THRESHOLD 2 | ||||
#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) | ||||
#define INTELSPI_SSPREG_SSCR1 0x4 | #define INTELSPI_SSPREG_SSCR1 0x4 | ||||
#define SSCR1_TINTE (1 << 19) | #define SSCR1_TINTE (1 << 19) | ||||
#define SSCR1_RFT(n) (((n) - 1) << 10) | #define SSCR1_RFT(n) (((n) - 1) << 10) | ||||
#define SSCR1_RFT_MASK (0xf << 10) | #define SSCR1_RFT_MASK (0xf << 10) | ||||
#define SSCR1_TFT(n) (((n) - 1) << 6) | #define SSCR1_TFT(n) (((n) - 1) << 6) | ||||
#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 | ||||
#define SSSR_RFL_MASK (0xf << 12) | #define SSSR_RFL_MASK (0xf << 12) | ||||
#define SSSR_TFL_MASK (0xf << 8) | #define SSSR_TFL_MASK (0xf << 8) | ||||
#define SSSR_RNE (1 << 3) | #define SSSR_RNE (1 << 3) | ||||
#define SSSR_TNF (1 << 2) | #define SSSR_TNF (1 << 2) | ||||
#define INTELSPI_SSPREG_SSITR 0xC | #define INTELSPI_SSPREG_SSITR 0xC | ||||
#define INTELSPI_SSPREG_SSDR 0x10 | #define INTELSPI_SSPREG_SSDR 0x10 | ||||
#define INTELSPI_SSPREG_SSTO 0x28 | #define INTELSPI_SSPREG_SSTO 0x28 | ||||
#define INTELSPI_SSPREG_SSPSP 0x2C | #define INTELSPI_SSPREG_SSPSP 0x2C | ||||
#define INTELSPI_SSPREG_SSTSA 0x30 | #define INTELSPI_SSPREG_SSTSA 0x30 | ||||
#define INTELSPI_SSPREG_SSRSA 0x34 | #define INTELSPI_SSPREG_SSRSA 0x34 | ||||
#define INTELSPI_SSPREG_SSTSS 0x38 | #define INTELSPI_SSPREG_SSTSS 0x38 | ||||
#define INTELSPI_SSPREG_SSACD 0x3C | #define INTELSPI_SSPREG_SSACD 0x3C | ||||
#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 | ||||
intelspi_txfifo_full(struct intelspi_softc *sc) | intelspi_txfifo_full(struct intelspi_softc *sc) | ||||
{ | { | ||||
uint32_t sssr; | uint32_t sssr; | ||||
INTELSPI_ASSERT_LOCKED(sc); | INTELSPI_ASSERT_LOCKED(sc); | ||||
▲ Show 20 Lines • Show All 138 Lines • ▼ Show 20 Lines | |||||
static void | static void | ||||
intelspi_init(struct intelspi_softc *sc) | intelspi_init(struct intelspi_softc *sc) | ||||
{ | { | ||||
uint32_t reg; | uint32_t reg; | ||||
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; | ||||
/* Data size */ | /* Data size */ | ||||
reg |= SSCR0_DSS(DATA_SIZE_8BITS); | reg |= SSCR0_DSS(DATA_SIZE_8BITS); | ||||
/* Enable SSP */ | /* Enable SSP */ | ||||
reg |= SSCR0_SSE; | reg |= SSCR0_SSE; | ||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, reg); | INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, reg); | ||||
} | } | ||||
static void | static void | ||||
intelspi_set_cs(struct intelspi_softc *sc, int level) | 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; | ||||
KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, | KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, | ||||
("TX/RX command sizes should be equal")); | ("TX/RX command sizes should be equal")); | ||||
KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, | KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, | ||||
("TX/RX data sizes should be equal")); | ("TX/RX data sizes should be equal")); | ||||
INTELSPI_LOCK(sc); | INTELSPI_LOCK(sc); | ||||
/* 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) | |||||
wulf: I suggest to use WAIT/DONTWAIT and INTR/NOINTR suffixes to be consistent with IIC/SMBUS. | |||||
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); | ||||
return (err); | return (err); | ||||
} | } | ||||
} | } | ||||
/* 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; | ||||
sc->sc_written = 0; | sc->sc_written = 0; | ||||
sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; | sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; | ||||
/* Enable CS */ | /* Enable CS */ | ||||
intelspi_set_cs(sc, CS_LOW); | intelspi_set_cs(sc, CS_LOW); | ||||
/* Wait the CS delay */ | |||||
spibus_get_cs_delay(child, &cs_delay); | |||||
DELAY(cs_delay); | |||||
/* Transfer as much as possible to FIFOs */ | /* 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 = 100; | |||||
wulfUnsubmitted Not Done Inline ActionsInterrupt and poll timeouts are different. Why? wulf: Interrupt and poll timeouts are different. Why? | |||||
while (!intelspi_transact(sc) && poll_limit-- > 0) | |||||
DELAY(1000); | |||||
if (poll_limit == 0) | |||||
device_printf(dev, "polling was stuck, transaction not finished\n"); | |||||
wulfUnsubmitted Not Done Inline ActionsSet err value here wulf: Set err value here | |||||
} else { | |||||
Not Done Inline Actionsintelspi_transfer returns EIO on timeout in interrupt mode. Both err values must be equal. wulf: intelspi_transfer returns EIO on timeout in interrupt mode. Both err values must be equal. | |||||
if (!intelspi_transact(sc)) { | if (!intelspi_transact(sc)) { | ||||
/* If FIFO is not large enough - enable interrupts */ | /* If FIFO is not large enough - enable interrupts */ | ||||
sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); | sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); | ||||
sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); | sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); | ||||
INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); | INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); | ||||
/* and wait for transaction to complete */ | /* and wait for transaction to complete */ | ||||
err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2); | err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2); | ||||
} | } | ||||
} | |||||
/* de-asser CS */ | /* De-assert CS */ | ||||
if ((cmd->flags & SPI_FLAG_KEEP_CS) == 0) | |||||
intelspi_set_cs(sc, CS_HIGH); | intelspi_set_cs(sc, CS_HIGH); | ||||
/* Clear transaction details */ | /* Clear transaction details */ | ||||
sc->sc_cmd = NULL; | sc->sc_cmd = NULL; | ||||
sc->sc_read = 0; | sc->sc_read = 0; | ||||
sc->sc_written = 0; | sc->sc_written = 0; | ||||
sc->sc_len = 0; | sc->sc_len = 0; | ||||
/* Make sure the SPI engine and interrupts are disabled. */ | /* Make sure the SPI engine and interrupts are disabled. */ | ||||
Show All 13 Lines | intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd) | ||||
if (err == EWOULDBLOCK) { | if (err == EWOULDBLOCK) { | ||||
device_printf(sc->sc_dev, "transfer timeout\n"); | device_printf(sc->sc_dev, "transfer timeout\n"); | ||||
err = EIO; | err = EIO; | ||||
} | } | ||||
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) { | ||||
device_printf(dev, "can't allocate memory resource\n"); | device_printf(dev, "can't allocate memory resource\n"); | ||||
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); | ||||
Not Done Inline ActionsYou should set RF_SHAREABLE flag at least for PCI attachments wulf: You should set RF_SHAREABLE flag at least for PCI attachments | |||||
Done Inline ActionsProbably fine for ACPI attachment as well… would be nice to test all of this on a baytrail device but I don't currently have any val_packett.cool: Probably fine for ACPI attachment as well… would be nice to test all of this on a baytrail… | |||||
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; | ||||
} | } | ||||
/* Hook up our interrupt handler. */ | /* Hook up our interrupt handler. */ | ||||
if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, | if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, | ||||
NULL, intelspi_intr, sc, &sc->sc_irq_ih)) { | NULL, intelspi_intr, sc, &sc->sc_irq_ih)) { | ||||
device_printf(dev, "cannot setup the interrupt handler\n"); | device_printf(dev, "cannot setup the interrupt handler\n"); | ||||
goto error; | goto error; | ||||
} | } | ||||
intelspi_init(sc); | intelspi_init(sc); | ||||
device_add_child(dev, "spibus", -1); | device_add_child(dev, "spibus", -1); | ||||
return (bus_generic_attach(dev)); | return (bus_delayed_attach_children(dev)); | ||||
Not Done Inline ActionsChildren attachment should be performed only after interrupts became available wulf: Children attachment should be performed only after interrupts became available | |||||
Done Inline Actionshmmm which interrupts? I don't think the SPI bus provides its own interrupts? The interrupts used internally in this controller, well… turns out I actually need to — at least optionally — avoid using them. val_packett.cool: hmmm which interrupts? I don't think the SPI bus provides its own interrupts?
The interrupts… | |||||
Not Done Inline Actions
controller interrupts. spibus_transfer does not work until PICs are enabled that happen very late in kernel boot sequence. If any child would request SPI transfer during probe or attach stage it would fail when intelspi is loaded from boot loader or compiled in to the kernel.
ig4 has a special support for interrupt-less (polling) mode: https://github.com/freebsd/freebsd-src/blob/c441592a0e1591591665cd037a8a5e9b54675f99/sys/dev/ichiic/ig4_iic.c#L311 . It spins rather than sleeps while waiting for new data from IIC bus. It looks that intelspi does not support polling mode yet. Just replace bus_generic_attach() with bus_delayed_attach_children() to fix it. Or implement before-mentioned mode. wulf: > hmmm which interrupts?
controller interrupts. spibus_transfer does not work until PICs are… | |||||
Done Inline ActionsYep, that's what I mean — I've implemented polling mode because I need it to be able to do SPI transactions in an interrupt context. Should I make it the default, with an "allow sleep" flag on the SPI message to use the old behavior? Or the other way around, add a "no sleep" flag?
Sure, looks like a good idea anyway val_packett.cool: Yep, that's what I mean — I've implemented polling mode because I need it to be able to do SPI… | |||||
Not Done Inline Actions
IMO Interrupt mode should be default and polled mode should be optional wulf: > Should I make it the default, with an "allow sleep" flag on the SPI message to use the old… | |||||
error: | error: | ||||
INTELSPI_LOCK_DESTROY(sc); | INTELSPI_LOCK_DESTROY(sc); | ||||
if (sc->sc_mem_res != NULL) | if (sc->sc_mem_res != NULL) | ||||
bus_release_resource(dev, SYS_RES_MEMORY, | bus_release_resource(dev, SYS_RES_MEMORY, | ||||
sc->sc_mem_rid, sc->sc_mem_res); | sc->sc_mem_rid, sc->sc_mem_res); | ||||
if (sc->sc_irq_res != NULL) | if (sc->sc_irq_res != NULL) | ||||
bus_release_resource(dev, SYS_RES_IRQ, | bus_release_resource(dev, SYS_RES_IRQ, | ||||
sc->sc_irq_rid, sc->sc_irq_res); | sc->sc_irq_rid, sc->sc_irq_res); | ||||
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; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
INTELSPI_LOCK_DESTROY(sc); | INTELSPI_LOCK_DESTROY(sc); | ||||
if (sc->sc_irq_ih) | if (sc->sc_irq_ih) | ||||
bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih); | bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih); | ||||
if (sc->sc_mem_res != NULL) | if (sc->sc_mem_res != NULL) | ||||
bus_release_resource(dev, SYS_RES_MEMORY, | bus_release_resource(dev, SYS_RES_MEMORY, | ||||
sc->sc_mem_rid, sc->sc_mem_res); | sc->sc_mem_rid, sc->sc_mem_res); | ||||
if (sc->sc_irq_res != NULL) | if (sc->sc_irq_res != NULL) | ||||
bus_release_resource(dev, SYS_RES_IRQ, | bus_release_resource(dev, SYS_RES_IRQ, | ||||
sc->sc_irq_rid, sc->sc_irq_res); | sc->sc_irq_rid, sc->sc_irq_res); | ||||
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); | ||||
}; | } | ||||
static devclass_t intelspi_devclass; | /* Shutdown just in case */ | ||||
DRIVER_MODULE(intelspi, acpi, intelspi_driver, intelspi_devclass, 0, 0); | INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0); | ||||
MODULE_DEPEND(intelspi, acpi, 1, 1, 1); | |||||
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)); | |||||
} |
I suggest to use WAIT/DONTWAIT and INTR/NOINTR suffixes to be consistent with IIC/SMBUS. NO_SLEEP sounds ok too.