glue code part of the spi kernel module, actual xfers is for tomorrow

This commit is contained in:
Triss 2021-07-16 03:43:04 +02:00
parent 41d352af3d
commit 444d805f0b
3 changed files with 580 additions and 1 deletions

View File

@ -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 KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd) PWD := $(shell pwd)

544
host/modules/spi-dmj.c Normal file
View File

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/pm_runtime.h>
#include <asm/unaligned.h>
#if 0
#include <linux/mfd/dmj.h>
#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 <sys64738@disroot.org>");
MODULE_AUTHOR("haskal <haskal@awoo.systems>");
MODULE_DESCRIPTION("SPI interface driver for the " HARDWARE_NAME " USB multitool");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dmj-spi");

View File

@ -71,6 +71,41 @@ enum serprog_caps {
#define SERPROG_IFACE_VERSION 0x0001 #define SERPROG_IFACE_VERSION 0x0001
/*
Its easy to be confused here, and the vendor documentation youll find isnt
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 its 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 wont 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 chips timing diagram shows the clock starting low (CPOL=0) and
data stabilized for sampling during the trailing clock edge (CPHA=1), thats
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. Thats why many devices support for example both modes 0 and
3: they dont care about polarity, and always clock data in/out on rising clock
edges.
- Linux kernel docs
*/
#ifdef DBOARD_HAS_SPI #ifdef DBOARD_HAS_SPI
/* functions to be implemented by the BSP */ /* functions to be implemented by the BSP */
uint32_t /*freq_applied*/ sp_spi_set_freq(uint32_t freq_wanted); uint32_t /*freq_applied*/ sp_spi_set_freq(uint32_t freq_wanted);