diff --git a/host/modules/Makefile b/host/modules/Makefile index d1a63ae..25c7309 100644 --- a/host/modules/Makefile +++ b/host/modules/Makefile @@ -1,5 +1,5 @@ -obj-m := dmj.o dmj-char.o i2c-dmj.o spi-dmj.c +obj-m := dmj.o dmj-char.o i2c-dmj.o #spi-dmj.c KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) diff --git a/host/modules/spi-dmj.c b/host/modules/spi-dmj.c new file mode 100644 index 0000000..3172284 --- /dev/null +++ b/host/modules/spi-dmj.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for the DapperMime-JTAG USB multitool: USB-SPI adapter + * + * Copyright (c) 2021 sys64738 and haskal + * + * Adapted from: + * spi-dln2.c, Copyright (c) 2014 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#if 0 +#include +#else +#include "dmj.h" +#endif + +#define HARDWARE_NAME "DapperMime-JTAG" + +#define DMJ_SPI_CMD_NOP 0x00 +#define DMJ_SPI_CMD_Q_IFACE 0x01 +#define DMJ_SPI_CMD_Q_CMDMAP 0x02 +#define DMJ_SPI_CMD_Q_PGMNAME 0x03 +#define DMJ_SPI_CMD_Q_SERBUF 0x04 +#define DMJ_SPI_CMD_Q_BUSTYPE 0x05 +#define DMJ_SPI_CMD_Q_CHIPSIZE 0x06 +#define DMJ_SPI_CMD_Q_OPBUF 0x07 +#define DMJ_SPI_CMD_Q_WRNMAXLEN 0x08 +#define DMJ_SPI_CMD_R_BYTE 0x09 +#define DMJ_SPI_CMD_R_NBYTES 0x0a +#define DMJ_SPI_CMD_O_INIT 0x0b +#define DMJ_SPI_CMD_O_WRITEB 0x0c +#define DMJ_SPI_CMD_O_WRITEN 0x0d +#define DMJ_SPI_CMD_O_DELAY 0x0e +#define DMJ_SPI_CMD_O_EXEC 0x0f +#define DMJ_SPI_CMD_SYNCNOP 0x10 +#define DMJ_SPI_CMD_Q_RDNMAXLEN 0x11 +#define DMJ_SPI_CMD_S_BUSTYPE 0x12 +#define DMJ_SPI_CMD_SPIOP 0x13 +#define DMJ_SPI_CMD_S_SPI_FREQ 0x14 +#define DMJ_SPI_CMD_S_PINSTATE 0x15 + +#define DMJ_SPI_CMD_Q_SPI_CAPS 0x40 +#define DMJ_SPI_CMD_S_SPI_CHIPN 0x41 +#define DMJ_SPI_CMD_S_SPI_SETCS 0x42 +#define DMJ_SPI_CMD_S_SPI_FLAGS 0x43 +#define DMJ_SPI_CMD_S_SPI_BPW 0x44 +#define DMJ_SPI_CMD_SPI_READ 0x45 +#define DMJ_SPI_CMD_SPI_WRITE 0x46 +#define DMJ_SPI_CMD_SPI_RDWR 0x47 + +#define SERPROG_IFACE_VERSION 0x0001 + +static const uint8_t reqd_cmds[] = { + DMJ_SPI_CMD_NOP, DMJ_SPI_CMD_Q_IFACE, DMJ_SPI_CMD_Q_CMDMAP, + DMJ_SPI_CMD_Q_WRNMAXLEN, DMJ_SPI_CMD_Q_RDNMAXLEN, + DMJ_SPI_CMD_S_SPI_FREQ, DMJ_SPI_CMD_S_PINSTATE, /*DMJ_SPI_CMD_SPIOP,*/ + DMJ_SPI_CMD_Q_SPI_CAPS, DMJ_SPI_CMD_S_SPI_CHIPN, DMJ_SPI_CMD_S_SPI_FLAGS, + DMJ_SPI_CMD_S_SPI_BPW, DMJ_SPI_CMD_S_SPI_SETCS, + /*DMJ_SPI_CMD_SPI_READ, DMJ_SPI_CMD_SPI_WRITE, DMJ_SPI_CMD_SPI_RDWR,*/ +}; + +#define DMJ_SPI_ACK 0x06 +#define DMJ_SPI_NAK 0x15 + +#define DMJ_SPI_S_FLG_CPHA (1<<0) +#define DMJ_SPI_S_FLG_CPOL (1<<1) +#define DMJ_SPI_S_FLG_STDSPI (0<<2) +#define DMJ_SPI_S_FLG_TISSP (1<<2) +#define DMJ_SPI_S_FLG_MICROW (2<<2) +#define DMJ_SPI_S_FLG_MSBFST (0<<4) +#define DMJ_SPI_S_FLG_LSBFST (1<<4) +#define DMJ_SPI_S_FLG_CSACLO (0<<5) +#define DMJ_SPI_S_FLG_CSACHI (1<<5) +#define DMJ_SPI_S_FLG_3WIRE (1<<6) + +#define DMJ_SPI_S_CAP_CPHA_HI (1<<0) +#define DMJ_SPI_S_CAP_CPHA_LO (1<<1) +#define DMJ_SPI_S_CAP_CPOL_HI (1<<2) +#define DMJ_SPI_S_CAP_CPOL_LO (1<<3) +#define DMJ_SPI_S_CAP_STDSPI (1<<4) +#define DMJ_SPI_S_CAP_TISSP (1<<5) +#define DMJ_SPI_S_CAP_MICROW (1<<6) +#define DMJ_SPI_S_CAP_MSBFST (1<<7) +#define DMJ_SPI_S_CAP_LSBFST (1<<8) +#define DMJ_SPI_S_CAP_CSACHI (1<<9) +#define DMJ_SPI_S_CAP_3WIRE (1<<10) + +struct dmj_spi_caps { + uint32_t freq_min, freq_max; + uint16_t flgcaps; + uint8_t num_cs, min_bpw, max_bpw; +}; +struct dmj_spi_dev_sett { + /* does not have to be guarded with a spinlock, as the kernel already + * serializes transfer_one/set_cs calls */ + uint32_t freq; + uint8_t flags, bpw; + uint8_t cs; +}; +struct dmj_spi { + struct platform_device *pdev; + struct spi_controller *spictl; + + uint8_t cmdmap[32]; + struct dmj_spi_caps caps; + uint8_t csmask; + struct dmj_spi_dev_sett devsettings[8]; + uint32_t wrnmaxlen, rdnmaxlen; + + spinlock_t csmap_lock; +}; + +static bool has_cmd(struct dmj_spi *dmjs, int cmd) +{ + int byteind = cmd >> 3, bitind = cmd & 7; + + return dmjs->cmdmap[byteind] & (1 << bitind); +} + +static uint8_t kernmode_to_flags(uint16_t caps, int mode) +{ + uint8_t ret = mode & 3; /* bottom 2 bits are the SPI mode (CPHA & CPOL) */ + + if (mode & SPI_LSB_FIRST) ret |= DMJ_SPI_S_FLG_LSBFST; + else ret |= DMJ_SPI_S_FLG_MSBFST; + if (mode & SPI_CS_HIGH) ret |= DMJ_SPI_S_FLG_CSACHI; + else ret |= DMJ_SPI_S_FLG_CSACLO; + if (mode & SPI_3WIRE) ret |= DMJ_SPI_SL_FLG_3WIRE; + + /* need some defaults for other stuff */ + if (caps & DMJ_SPI_S_CAP_STDSPI) ret |= DMJ_SPI_S_FLG_STDSPI; + else if (caps & DMJ_SPI_S_CAP_TISSP) ret |= DMJ_SPI_S_FLG_TISSP; + else if (caps & DMJ_SPI_S_CAP_MICROW) ret |= DMJ_SPI_S_FLG_MICROW; + else ret |= DMJ_SPI_S_FLG_STDSPI; /* shrug */ + + return ret; +} +static int devcaps_to_kernmode(uint16_t caps) +{ + int ret; + + ret = caps & 3; /* SPI mode (CPHA & CPOL) bits */ + + if (caps & DMJ_SPI_S_CAP_LSBFST) ret |= SPI_LB_FIRST; + if (caps & DMJ_SPI_S_CAP_CSACHI) ret |= SPI_CS_HIGH; + if (caps & DMJ_SPI_S_CAP_3WIRE) ret |= SPI_3WIRE; + + return ret; +} + +static int bufconv_to_le(void *dst, const void *src, size_t len_bytes, uint8_t bpw) +{ +#ifdef __LITTLE_ENDIAN + memcpy(dst, src, len_bytes); +#else + if (bpw > 16) { + __le32 *dst32 = (__le32 *)dst; + const uint32_t *src32 = (const uint32_t *)src; + + for (size_t i = 0; i < len_bytes; i += 4, ++dst32, ++src32) { + *dst32 = cpu_to_le32p(src32); + } + } else if (bpw > 8) { + __le16 *dst16 = (__le16 *)dst; + const uint16_t *src16 = (const uint16_t *)src; + + for (size_t i = 0; i < len_bytes; i += 2, ++dst16, ++src16) { + *dst16 = cpu_to_le16p(src16); + } + } else { + memcpy(dst, src, len_bytes); + } +#endif +} +static int bufconv_from_le(void *dst, const void *src, size_t len_bytes, uint8_t bpw) +{ +#ifdef __LITTLE_ENDIAN + memcpy(dst, src, len_bytes); +#else + if (bpw > 16) { + const __le32 *src32 = (const __le32 *)src; + uint32_t *dst32 = (uint32_t *)dst; + + for (size_t i = 0; i < len_bytes; i += 4, ++dst32, ++src32) { + *dst32 = get_unaligned_le32(src32); + } + } else if (bpw > 8) { + const __le16 *src16 = (const __le16 *)src; + uint16_t *dst16 = (uint16_t *)dst; + + for (size_t i = 0; i < len_bytes; i += 2, ++dst16, ++src16) { + *dst16 = le16_to_cpup(src16); + } + } else { + memcpy(dst, src, len_bytes); + } +#endif +} + +static int dmj_spi_csmask_set(struct dmj_spi *dmjs, uint8_t csmask) +{ + uint8_t oldcm; + int ret; + bool do_csmask = false; + + spin_lock(&dmjs->csmap_lock); + oldcm = dmjs->csmask; + if (oldcm != csmask) { + dmjs->csmask = csmask; + do_csmask = true; + } + spin_unlock(&dmjs->csmap_unlock); + + if (do_csmask) { + // TODO: send S_SPI_CHIPN + } +} +static int dmj_spi_csmask_set_one(struct dmj_spi *dmjs, uint8_t cs) +{ + return dmj_spi_csmask_set(dmjs, BIT(cs)); +} +static int dmj_spi_cs_set(struct dmj_spi *dmjs, int ind, bool lvl) +{ + if (dmjs->devsettings[ind].cs == (lvl ? 1 : 0)) + return 0; + + // TODO: send S_SPI_SETCS +} +static int dmj_spi_get_caps(struct dmj_spi *dmjs); // TODO: check if bpw_min > 0 +static int dmj_spi_set_freq(struct dmj_spi *dmjs, int ind, uint32_t freq) +{ + if (dmjs->devsettings[ind].freq == freq) + return 0; + + // TODO: send S_SPI_FREQ +} +static int dmj_spi_set_flags(struct dmj_spi *dmjs, int ind, uint8_t flags) +{ + if (dmjs->devsettings[ind].flags == flags) + return 0; + + // TODO: send S_SPI_FLAGS +} +static int dmj_spi_set_bpw(struct dmj_spi *dmjs, int ind, uint8_t bpw) +{ + if (dmjs->devsettings[ind].bpw == bpw) + return 0; + + + // TODO: send S_SPI_BPW +} +static int dmj_spi_set_pinstate(struct dmj_spi *dmjs, bool pins) +{ + if (!has_cmd(dmjs, DMJ_SPI_CMD_S_PINSTATE)) + return 0; + + // TODO: do cmd +} + +static int dmj_spi_check_hw(struct dmj_spi *dmjs) +{ + /* + * TODO: check: + * * NOP, SYNCNOP + * * Q_IFACE retval (must be SERPROG_IFACE_VERSION) + * * ^ + SPIOP -or- READ & WRITE support + * * Q_CMDMAP (cf. reqd_cmds) + * * RDNMAXLEN, WRNMAXLEN + */ +} + +static int dmj_spi_do_read(struct dmj_spi *dmjs, void *data, size_t len) +{ + if (has_cmd(dmjs, DMJ_SPI_CMD_SPI_READ)) { + /* TODO: do SPI_READ */ + } else { + /* TODO: do SPIOP */ + } +} +static int dmj_spi_do_write(struct dmj_spi *dmjs, const void *data, size_t len) +{ + if (has_cmd(dmjs, DMJ_SPI_CMD_SPI_WRITE)) { + /* TODO: do SPI_WRITE */ + } else { + /* TODO: do SPIOP */ + } +} +/* should only be called if it already has the cmd anyway (cf. spi_controller->flags) */ +static int dmj_spi_do_rdwr(struct dmj_spi *dmjs, void *rdata, const void *wdata, size_t len); + +static int dmj_spi_prepare_message(struct spi_controller *spictl, struct spi_message *msg) +{ + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + struct spi_device *spidev = message->spi; + struct device *dev = &spidev->dev; + int ret; + + ret = dmj_spi_set_flags(dmjs, spidev->chip_select, kernmode_to_flags(spidev->mode)); + if (ret < 0) { + dev_err(dev, "Failed to set SPI flags\n"); + return ret; + } + + /*ret = dmj_spi_csmask_set_one(dmjs, spidev->chip_select); + if (ret < 0) { + dev_err(dev, "Failed to set CS mask\n"); + return ret; + }*/ + + return ret; +} +static int dmj_spi_set_cs(struct spi_device *spidev, bool enable) +{ + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + struct spi_device *spidev = message->spi; + struct device *dev = &spidev->dev; + int ret; + + ret = dmj_spi_csmask_set_one(dmjs, spidev->chip_select); + if (ret < 0) { + dev_err(dev, "Failed to set CS mask\n"); + return ret; + } + + ret = dmj_spi_cs_set(dmjs, spidev->chip_select, enable); + if (ret < 0) { + dev_err(dev, "Failed to set chip select line\n"); + return ret; + } + + return 0; +} +static int dmj_spi_transfer_one(struct spi_controller *spictl, struct spi_device *spidev, struct spi_transfer *xfer) +{ + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + struct device *dev = &spidev->dev; + int ret; + uint32_t cksize, todo, off = 0; + + ret = dmj_spi_set_freq(dmjs, spidev->chip_select, xfer->speed_hz); + if (ret < 0) { + dev_err(dev, "Failed to set SPI frequency to %d Hz\n", xfer->speed_hz); + return ret; + } + + ret = dmj_spi_set_bpw(dmjs, spidev->chip_select, xfer->bpw); + if (ret < 0) { + dev_err(dev, "Failed to set SPI bits-per-word to %d\n", xfer->bits_per_word); + return ret; + } + + cksize; + if (xfer->tx_buf && xfer->rx_buf) { + cksize = dmjs->wrnmaxlen; + if (cksize > dmjs->rdnmaxlen) cksize = dmjs->rdnmaxlen; + } else if (xfer->tx_buf) { + cksize = dmjs->wrnmaxlen; + } else if (xfer->rx_buf) { + cksize = dmjs->rdnmaxlen; + } else return -EINVAL; + + todo = xfer->len; + do { + if (todo < cksize) cksize = todo; + + if (xfer->tx_buf && xfer->rx_buf) { + ret = dmj_spi_do_rdwr(dmjs, xfer->rx_buf + off, xfer->tx_buf + off, cksize); + } else if (xfer->tx_buf) { + ret = dmj_spi_do_write(dmjs, xfer->tx_buf + off, cksize); + } else /*if (xfer->rx_buf)*/ { + ret = dmj_spi_do_read(dmjs, xfer->rx_Buf + off, cksize); + } + + if (ret < 0) return ret; + + todo -= cksize; + off += cksize; + } while (todo); + + return 0; +} + +static int dmj_spi_probe(struct platform_device *pdev) +{ + struct spi_controller *spictl; + struct dmj_spi *dmjs; + struct device *dev = &pdev->dev; + int ret; + + controller = spi_alloc_controller(dev, sizeof(*dmjs)); + if (!controller) return -ENOMEM; + + platform_set_drvdata(pdev, controller); + + dmjs = spi_controller_get_devdata(spictl); + + dmjs->spictl = spictl; + dmjs->spictl->dev.of_node = dev->of_node; + dmjs->pdev = pdev; + dmjs->csmask = 0xff; + for (i = 0; i < 8; ++i) { + dmjs->devsettings[i].freq = 0; + dmjs->devsettings[i].flags = 0xff; + dmjs->devsettings[i].bpw = 0xff; + } + + spin_lock_init(&dmjs->csmap_lock); + + ret = dmj_spi_check_hw(dmjs); + if (ret < 0) { + dev_err(dev, "Hardware capabilities lacking\n"); + goto err_free_ctl; + } + + ret = dmj_spi_get_caps(dmjs); + if (ret < 0) { + dev_err(dev, "Failed to get device capabilities\n"); + goto err_free_ctl; + } + + spictl->min_speed_hz = dmjs->caps.freq_min; + spictl->max_speed_hz = dmjs->caps.freq_max; + spictl->bits_per_word_mask = SPI_BPW_RANGE_MASK(dmjs->caps.min_bpw, dmjs->caps.max_bpw); + spictl->num_chipselect = dmjs->caps.num_cs; + spictl->mode_bits = devcaps_to_kernmode(dmjs->caps.flgcaps); + + spictl->bus_num = -1; + spictl->prepare_message = dmj_spi_prepare_message; + spictl->transfer_one = dmj_spi_transfer_one; + spictl->set_cs = dmj_spi_set_cs; + + spictl->flags = 0; + if (!has_cmd(dmjs, DMJ_SPI_CMD_SPI_RDWR)) + spictl->flags |= SPI_CONTROLLER_HALF_DUPLEX; + + pm_runtime_set_autosuspend_delay(dev, DMJ_RPM_AUTOSUSPEND_TIMEOUT); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_set_enable(dev); + + ret = devm_spi_register_controller(dev, spictl); + if (ret < 0) { + dev_err(dev, "Failed to register SPI controller\n"); + goto err_dereg; + } + + return dmj_spi_set_pinstate(dmjs, true); + +err_dereg: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); +err_free_ctl: + spi_controller_put(spictl); + return ret; +} + +static int dmj_spi_remove(struct platform_device *pdev) +{ + struct spi_controller *spictl = platform_get_drvdata(pdev); + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + + pm_runtime_disable(&pdev->dev); + + dmj_spi_set_pinstate(dmjs, false); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dmj_spi_suspend(struct device *dev) +{ + struct spi_controller *spictl = dev_get_drvdata(dev); + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + int ret, i; + + ret = spi_controller_suspend(spictl); + if (ret < 0) return ret; + + dmj_spi_set_pinstate(dmjs, false); + + for (i = 0; i < 8; ++i) { + dmjs->devsettings[i].freq = 0; + dmjs->devsettings[i].flags = 0xff; + dmjs->devsettings[i].bpw = 0xff; + } + + return 0; +} + +static int dmj_spi_resume(struct device *dev) +{ + struct spi_controller *spictl = dev_get_drvdata(dev); + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + + dmj_spi_set_pinstate(dmjs, true); + + return spi_controller_resume(spictl); +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int dmj_spi_runtime_suspend(struct device *dev) +{ + struct spi_controller *spictl = dev_get_drvdata(dev); + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + + return dmj_spi_set_pinstate(dmjs, false); +} +static int dmj_spi_runtime_resume(struct device *dev) +{ + struct spi_controller *spictl = dev_get_drvdata(dev); + struct dmj_spi *dmjs = spi_controller_get_devdata(spictl); + + return dmj_spi_set_pinstate(dmjs, true); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops dmj_spi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(dmj_spi_suspend, dmj_spi_resume) + SET_RUNTIME_PM_OPS(dmj_spi_runtime_suspend, dmj_spi_runtime_resume, NULL) +}; + +static struct platform_driver spi_dmj_driver = { + .driver = { + .name = "dmj-spi", + .pm = &dmj_spi_pm, + }, + .probe = dmj_spi_probe, + .remove = dmj_spi_remove +}; +module_platform_driver(spi_dmj_driver); + +MODULE_AUTHOR("sys64738 "); +MODULE_AUTHOR("haskal "); +MODULE_DESCRIPTION("SPI interface driver for the " HARDWARE_NAME " USB multitool"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dmj-spi"); diff --git a/src/m_default/serprog.h b/src/m_default/serprog.h index 9ef0fb1..fd00be9 100644 --- a/src/m_default/serprog.h +++ b/src/m_default/serprog.h @@ -71,6 +71,41 @@ enum serprog_caps { #define SERPROG_IFACE_VERSION 0x0001 +/* +It’s easy to be confused here, and the vendor documentation you’ll find isn’t +necessarily helpful. The four modes combine two mode bits: + + CPOL indicates the initial clock polarity. CPOL=0 means the clock + starts low, so the first (leading) edge is rising, and the second + (trailing) edge is falling. CPOL=1 means the clock starts high, so the + first (leading) edge is falling. + + CPHA indicates the clock phase used to sample data; CPHA=0 says sample + on the leading edge, CPHA=1 means the trailing edge. + + Since the signal needs to stablize before it’s sampled, CPHA=0 implies + that its data is written half a clock before the first clock edge. The + chipselect may have made it become available. + +Chip specs won’t always say “uses SPI mode X” in as many words, but their +timing diagrams will make the CPOL and CPHA modes clear. + +In the SPI mode number, CPOL is the high order bit and CPHA is the low order +bit. So when a chip’s timing diagram shows the clock starting low (CPOL=0) and +data stabilized for sampling during the trailing clock edge (CPHA=1), that’s +SPI mode 1. + +Note that the clock mode is relevant as soon as the chipselect goes active. So +the master must set the clock to inactive before selecting a slave, and the +slave can tell the chosen polarity by sampling the clock level when its select +line goes active. That’s why many devices support for example both modes 0 and +3: they don’t care about polarity, and always clock data in/out on rising clock +edges. + + + - Linux kernel docs +*/ + #ifdef DBOARD_HAS_SPI /* functions to be implemented by the BSP */ uint32_t /*freq_applied*/ sp_spi_set_freq(uint32_t freq_wanted);