985 lines
28 KiB
C
985 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Driver for the Dragon Probe 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/dragonprobe.h>
|
|
#else
|
|
#include "dragonprobe.h"
|
|
#endif
|
|
|
|
#define HARDWARE_NAME "Dragon Probe"
|
|
|
|
#define DP_SPI_CMD_NOP 0x00
|
|
#define DP_SPI_CMD_Q_IFACE 0x01
|
|
#define DP_SPI_CMD_Q_CMDMAP 0x02
|
|
#define DP_SPI_CMD_Q_PGMNAME 0x03
|
|
#define DP_SPI_CMD_Q_SERBUF 0x04
|
|
#define DP_SPI_CMD_Q_BUSTYPE 0x05
|
|
#define DP_SPI_CMD_Q_CHIPSIZE 0x06
|
|
#define DP_SPI_CMD_Q_OPBUF 0x07
|
|
#define DP_SPI_CMD_Q_WRNMAXLEN 0x08
|
|
#define DP_SPI_CMD_R_BYTE 0x09
|
|
#define DP_SPI_CMD_R_NBYTES 0x0a
|
|
#define DP_SPI_CMD_O_INIT 0x0b
|
|
#define DP_SPI_CMD_O_WRITEB 0x0c
|
|
#define DP_SPI_CMD_O_WRITEN 0x0d
|
|
#define DP_SPI_CMD_O_DELAY 0x0e
|
|
#define DP_SPI_CMD_O_EXEC 0x0f
|
|
#define DP_SPI_CMD_SYNCNOP 0x10
|
|
#define DP_SPI_CMD_Q_RDNMAXLEN 0x11
|
|
#define DP_SPI_CMD_S_BUSTYPE 0x12
|
|
#define DP_SPI_CMD_SPIOP 0x13
|
|
#define DP_SPI_CMD_S_SPI_FREQ 0x14
|
|
#define DP_SPI_CMD_S_PINSTATE 0x15
|
|
|
|
#define DP_SPI_CMD_Q_SPI_CAPS 0x40
|
|
#define DP_SPI_CMD_S_SPI_CHIPN 0x41
|
|
#define DP_SPI_CMD_S_SPI_SETCS 0x42
|
|
#define DP_SPI_CMD_S_SPI_FLAGS 0x43
|
|
#define DP_SPI_CMD_S_SPI_BPW 0x44
|
|
#define DP_SPI_CMD_SPI_READ 0x45
|
|
#define DP_SPI_CMD_SPI_WRITE 0x46
|
|
#define DP_SPI_CMD_SPI_RDWR 0x47
|
|
|
|
#define SERPROG_IFACE_VERSION 0x0001
|
|
|
|
static const uint8_t reqd_cmds[] = {
|
|
DP_SPI_CMD_NOP, DP_SPI_CMD_Q_IFACE, DP_SPI_CMD_Q_CMDMAP,
|
|
/*DP_SPI_CMD_Q_WRNMAXLEN, DP_SPI_CMD_Q_RDNMAXLEN,*/
|
|
DP_SPI_CMD_S_SPI_FREQ, /*DP_SPI_CMD_S_PINSTATE,*/ /*DP_SPI_CMD_SPIOP,*/
|
|
DP_SPI_CMD_Q_SPI_CAPS, DP_SPI_CMD_S_SPI_CHIPN, DP_SPI_CMD_S_SPI_FLAGS,
|
|
DP_SPI_CMD_S_SPI_BPW, DP_SPI_CMD_S_SPI_SETCS,
|
|
/*DP_SPI_CMD_SPI_READ, DP_SPI_CMD_SPI_WRITE, DP_SPI_CMD_SPI_RDWR,*/
|
|
};
|
|
|
|
#define DP_SPI_ACK 0x06
|
|
#define DP_SPI_NAK 0x15
|
|
|
|
#define DP_SPI_S_FLG_CPHA (1<<0)
|
|
#define DP_SPI_S_FLG_CPOL (1<<1)
|
|
#define DP_SPI_S_FLG_STDSPI (0<<2)
|
|
#define DP_SPI_S_FLG_TISSP (1<<2)
|
|
#define DP_SPI_S_FLG_MICROW (2<<2)
|
|
#define DP_SPI_S_FLG_MSBFST (0<<4)
|
|
#define DP_SPI_S_FLG_LSBFST (1<<4)
|
|
#define DP_SPI_S_FLG_CSACLO (0<<5)
|
|
#define DP_SPI_S_FLG_CSACHI (1<<5)
|
|
#define DP_SPI_S_FLG_3WIRE (1<<6)
|
|
|
|
#define DP_SPI_S_CAP_CPHA_HI (1<<0)
|
|
#define DP_SPI_S_CAP_CPHA_LO (1<<1)
|
|
#define DP_SPI_S_CAP_CPOL_HI (1<<2)
|
|
#define DP_SPI_S_CAP_CPOL_LO (1<<3)
|
|
#define DP_SPI_S_CAP_STDSPI (1<<4)
|
|
#define DP_SPI_S_CAP_TISSP (1<<5)
|
|
#define DP_SPI_S_CAP_MICROW (1<<6)
|
|
#define DP_SPI_S_CAP_MSBFST (1<<7)
|
|
#define DP_SPI_S_CAP_LSBFST (1<<8)
|
|
#define DP_SPI_S_CAP_CSACHI (1<<9)
|
|
#define DP_SPI_S_CAP_3WIRE (1<<10)
|
|
|
|
#define DP_PINST_AUTOSUSPEND_TIMEOUT 2000
|
|
|
|
struct dp_spi_caps {
|
|
uint32_t freq_min, freq_max;
|
|
uint16_t flgcaps;
|
|
uint8_t num_cs, min_bpw, max_bpw;
|
|
};
|
|
struct dp_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, pinst;
|
|
};
|
|
struct dp_spi {
|
|
struct platform_device *pdev;
|
|
struct spi_controller *spictl;
|
|
|
|
uint8_t *txbuf;
|
|
struct dp_spi_caps caps;
|
|
uint8_t csmask;
|
|
struct dp_spi_dev_sett devsettings[8];
|
|
uint32_t wrnmaxlen, rdnmaxlen;
|
|
uint8_t cmdmap[32];
|
|
|
|
spinlock_t csmap_lock;
|
|
|
|
struct spi_board_info binfo[8];
|
|
};
|
|
|
|
static int dp_check_retval_sp(int ret, int len, struct device *dev,
|
|
const char *pfix, bool check_pos_val, int lmin, int lmax, const void* rbuf)
|
|
{
|
|
ret = dp_check_retval(ret, len, dev, pfix, check_pos_val, lmin, lmax);
|
|
if (ret >= 0 && ((const uint8_t *)rbuf)[0] != DP_SPI_ACK) {
|
|
dev_err(dev, "%s: did not receive ACK\n", pfix);
|
|
ret = -EIO;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool has_cmd(struct dp_spi *dps, int cmd)
|
|
{
|
|
int byteind = cmd >> 3, bitind = cmd & 7;
|
|
|
|
return dps->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 |= DP_SPI_S_FLG_LSBFST;
|
|
else ret |= DP_SPI_S_FLG_MSBFST;
|
|
if (mode & SPI_CS_HIGH) ret |= DP_SPI_S_FLG_CSACHI;
|
|
else ret |= DP_SPI_S_FLG_CSACLO;
|
|
if (mode & SPI_3WIRE) ret |= DP_SPI_S_FLG_3WIRE;
|
|
|
|
/* need some defaults for other stuff */
|
|
if (caps & DP_SPI_S_CAP_STDSPI) ret |= DP_SPI_S_FLG_STDSPI;
|
|
else if (caps & DP_SPI_S_CAP_TISSP) ret |= DP_SPI_S_FLG_TISSP;
|
|
else if (caps & DP_SPI_S_CAP_MICROW) ret |= DP_SPI_S_FLG_MICROW;
|
|
else ret |= DP_SPI_S_FLG_STDSPI; /* shrug, also shouldn't happen (cf. get_caps) */
|
|
|
|
return ret;
|
|
}
|
|
static int devcaps_to_kernmode(uint16_t caps)
|
|
{
|
|
int ret;
|
|
|
|
ret = caps & 3; /* SPI mode (CPHA & CPOL) bits */
|
|
|
|
if (caps & DP_SPI_S_CAP_LSBFST) ret |= SPI_LSB_FIRST;
|
|
if (caps & DP_SPI_S_CAP_CSACHI) ret |= SPI_CS_HIGH;
|
|
if (caps & DP_SPI_S_CAP_3WIRE) ret |= SPI_3WIRE;
|
|
|
|
return ret;
|
|
}
|
|
static void caps_to_binfo(struct dp_spi *dps, int busnum)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < dps->caps.num_cs; ++i) {
|
|
snprintf(dps->binfo[i].modalias, SPI_NAME_SIZE, "spidev");
|
|
dps->binfo[i].controller_data = dps;
|
|
dps->binfo[i].max_speed_hz = dps->caps.freq_max;
|
|
dps->binfo[i].bus_num = busnum;
|
|
dps->binfo[i].chip_select = i;
|
|
dps->binfo[i].mode = 0; /* shrug */
|
|
}
|
|
}
|
|
|
|
static void 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 void 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 dp_spi_csmask_set(struct dp_spi *dps, uint8_t csmask)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t oldcm;
|
|
bool do_csmask = false;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_SPI_CHIPN, csmask };
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
spin_lock(&dps->csmap_lock);
|
|
oldcm = dps->csmask;
|
|
if (oldcm != csmask) {
|
|
dps->csmask = csmask;
|
|
do_csmask = true;
|
|
}
|
|
spin_unlock(&dps->csmap_lock);
|
|
|
|
if (do_csmask) {
|
|
dev_dbg(dev, "set csmask %02x\n", csmask);
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set CS mask", true, 1, 1, rbuf);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static int dp_spi_csmask_set_one(struct dp_spi *dps, uint8_t cs)
|
|
{
|
|
return dp_spi_csmask_set(dps, BIT(cs));
|
|
}
|
|
static int dp_spi_cs_set(struct dp_spi *dps, int ind, bool lvl)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_SPI_SETCS, lvl ? 1 : 0 };
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
if (dps->devsettings[ind].cs == (lvl ? 1 : 0)) return 0;
|
|
|
|
dev_dbg(dev, "set cs %s\n", lvl?"hi":"lo");
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set CS", true, 1, 1, rbuf);
|
|
|
|
if (!ret) {
|
|
dps->devsettings[ind].cs = lvl ? 1 : 0;
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
static int dp_spi_get_caps(struct dp_spi *dps)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_Q_SPI_CAPS };
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
dev_dbg(dev, "get caps\n");
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "get caps", true, 14, 14, rbuf);
|
|
|
|
if (!ret) {
|
|
dps->caps.freq_min = (uint32_t)rbuf[1] | ((uint32_t)rbuf[2] << 8)
|
|
| ((uint32_t)rbuf[3] << 16) | ((uint32_t)rbuf[4] << 24);
|
|
dps->caps.freq_max = (uint32_t)rbuf[5] | ((uint32_t)rbuf[6] << 8)
|
|
| ((uint32_t)rbuf[7] << 16) | ((uint32_t)rbuf[8] << 24);
|
|
dps->caps.flgcaps = (uint32_t)rbuf[9] | ((uint32_t)rbuf[10] << 8);
|
|
|
|
dps->caps.num_cs = rbuf[11];
|
|
dps->caps.min_bpw = rbuf[12];
|
|
dps->caps.max_bpw = rbuf[13];
|
|
|
|
dev_info(dev, " capabilities: freq=%d..%d, flgcaps=%04hx, bpw=%d..%d, num cs=%d\n",
|
|
dps->caps.freq_min, dps->caps.freq_max, dps->caps.flgcaps,
|
|
dps->caps.min_bpw, dps->caps.max_bpw, dps->caps.num_cs);
|
|
|
|
if (dps->caps.max_bpw == 0 || dps->caps.min_bpw == 0) {
|
|
dev_err(dev, "Device replied with max_bpw=0 or min_bpw=0, wtf?\n");
|
|
ret = -EXDEV;
|
|
}
|
|
if (!(dps->caps.flgcaps & (DP_SPI_S_CAP_STDSPI
|
|
| DP_SPI_S_CAP_TISSP | DP_SPI_S_CAP_MICROW))) {
|
|
dev_err(dev, "Device does not support any SPI mode, wtf?\n");
|
|
ret = -EXDEV;
|
|
}
|
|
|
|
kfree(rbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
static int dp_spi_set_freq(struct dp_spi *dps, int ind, uint32_t freq)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_SPI_FREQ,
|
|
freq, freq >> 8, freq >> 16, freq >> 24 };
|
|
uint8_t *rbuf;
|
|
uint32_t freqret;
|
|
int ret, rlen;
|
|
|
|
if (dps->devsettings[ind].freq == freq) return 0;
|
|
|
|
dev_dbg(dev, "set freq to %u\n", freq);
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set CS", true, 5, 5, rbuf);
|
|
|
|
if (!ret) {
|
|
freqret = (uint32_t)rbuf[1] | ((uint32_t)rbuf[2] << 8)
|
|
| ((uint32_t)rbuf[3] << 16) | ((uint32_t)rbuf[4] << 24);
|
|
|
|
if (freqret != freq) {
|
|
dev_warn(dev, "set frequency: couldn't provide exact freq %u Hz, %u Hz was applied instead.\n",
|
|
freq, freqret);
|
|
}
|
|
|
|
/* not the returned one, to avoid resending */
|
|
dps->devsettings[ind].freq = freq;
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
static int dp_spi_set_flags(struct dp_spi *dps, int ind, uint8_t flags)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_SPI_FLAGS, flags };
|
|
uint8_t *rbuf, flagret;
|
|
int ret, rlen;
|
|
|
|
if (dps->devsettings[ind].flags == flags) return 0;
|
|
|
|
dev_dbg(dev, "set flags %08x\n", flags);
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set flags", true, 2, 2, rbuf);
|
|
|
|
if (!ret) {
|
|
flagret = rbuf[1];
|
|
|
|
if (flagret != flags) {
|
|
dev_warn(dev, "set flags: couldn't set exact flags %08x, was set to %08x instead\n",
|
|
flags, flagret);
|
|
}
|
|
|
|
/* not the returned one, to avoid resending */
|
|
dps->devsettings[ind].flags = flags;
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
static int dp_spi_set_bpw(struct dp_spi *dps, int ind, uint8_t bpw)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_SPI_BPW, bpw };
|
|
uint8_t *rbuf, bpwret;
|
|
int ret, rlen;
|
|
|
|
if (dps->devsettings[ind].bpw == bpw) return 0;
|
|
|
|
dev_dbg(dev, "set bpw %hhu\n", bpw);
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set bpw", true, 2, 2, rbuf);
|
|
|
|
if (!ret) {
|
|
bpwret = rbuf[1];
|
|
|
|
if (bpwret != bpw) {
|
|
dev_warn(dev, "set flags: couldn't set exact bpw %hhu, was set to %hhu instead\n",
|
|
bpw, bpwret);
|
|
}
|
|
|
|
/* not the returned one, to avoid resending */
|
|
dps->devsettings[ind].bpw = bpw;
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
static int dp_spi_set_pinstate(struct dp_spi *dps, bool pins)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_S_PINSTATE, pins ? 1 : 0 };
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
if (!has_cmd(dps, DP_SPI_CMD_S_PINSTATE)) return 0;
|
|
|
|
dev_dbg(dev, "set pinstate %sabled\n", pins?"en":"dis");
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "set pinstate", true, 2, 2, rbuf);
|
|
|
|
/*if (!ret) {
|
|
dps->devsettings[ind].pinst = pins;
|
|
}*/
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dp_spi_check_hw(struct dp_spi *dps)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t wbuf[] = { DP_SPI_CMD_NOP };
|
|
uint8_t *rbuf;
|
|
uint16_t iface;
|
|
int ret, rlen, i;
|
|
|
|
dev_dbg(dev, "check hw: nop");
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: nop", true, 1, 1, rbuf);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
|
|
dev_dbg(dev, "check hw: syncnop");
|
|
wbuf[0] = DP_SPI_CMD_SYNCNOP;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval(ret, rlen, dev, "check hw: nop", true, 2, 2);
|
|
if (!ret) {
|
|
if (rbuf[0] != DP_SPI_NAK || rbuf[1] != DP_SPI_ACK) {
|
|
dev_err(dev, "check hw: syncnop: bad response %02x %02x\n",
|
|
rbuf[0], rbuf[1]);
|
|
ret = -EIO;
|
|
}
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
|
|
dev_dbg(dev, "check hw: iface");
|
|
wbuf[0] = DP_SPI_CMD_Q_IFACE;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: iface", true, 3, 3, rbuf);
|
|
|
|
if (!ret) {
|
|
iface = (uint16_t)rbuf[1] | ((uint16_t)rbuf[2] << 8);
|
|
|
|
if (iface != SERPROG_IFACE_VERSION) {
|
|
dev_err(dev, "check hw: iface: bad serprog version: expected %hu, got %hu\n",
|
|
SERPROG_IFACE_VERSION, iface);
|
|
ret = -ENODEV;
|
|
}
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
|
|
dev_dbg(dev, "check hw: cmdmap");
|
|
wbuf[0] = DP_SPI_CMD_Q_CMDMAP;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: cmdmap", true, 33, 33, rbuf);
|
|
|
|
if (!ret) {
|
|
memcpy(dps->cmdmap, &rbuf[1], 32);
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
|
|
for (i = 0; i < sizeof(reqd_cmds)/sizeof(*reqd_cmds); ++i) {
|
|
if (!has_cmd(dps, reqd_cmds[i])) {
|
|
dev_err(dev, "device does not have required serprog command %02x\n", reqd_cmds[i]);
|
|
ret = -ENODEV;
|
|
}
|
|
}
|
|
|
|
if (has_cmd(dps, DP_SPI_CMD_Q_PGMNAME)) {
|
|
dev_dbg(dev, "check hw: pgmname");
|
|
wbuf[0] = DP_SPI_CMD_Q_PGMNAME;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: pgmname", true, 17, 17, rbuf);
|
|
|
|
if (!ret) {
|
|
rbuf[16] = 0;
|
|
dev_info(dev, "Serprog pgmname: '%s'\n", &rbuf[1]);
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
}
|
|
|
|
if (has_cmd(dps, DP_SPI_CMD_Q_WRNMAXLEN)) {
|
|
dev_dbg(dev, "check hw: wrnmaxlen");
|
|
wbuf[0] = DP_SPI_CMD_Q_WRNMAXLEN;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: wrnmaxlen", true, 4, 4, rbuf);
|
|
|
|
if (!ret) {
|
|
dps->wrnmaxlen = (uint32_t)rbuf[1] | ((uint32_t)rbuf[2] << 8) | ((uint32_t)rbuf[3] << 16);
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
} else dps->rdnmaxlen = 512;
|
|
dev_info(dev, " wrnmaxlen = 0x%x\n", dps->wrnmaxlen);
|
|
|
|
if (has_cmd(dps, DP_SPI_CMD_Q_RDNMAXLEN)) {
|
|
dev_dbg(dev, "check hw: rdnmaxlen");
|
|
wbuf[0] = DP_SPI_CMD_Q_RDNMAXLEN;
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
wbuf, sizeof(wbuf), (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "check hw: rdnmaxlen", true, 4, 4, rbuf);
|
|
|
|
if (!ret) {
|
|
dps->rdnmaxlen = (uint32_t)rbuf[1] | ((uint32_t)rbuf[2] << 8) | ((uint32_t)rbuf[3] << 16);
|
|
}
|
|
if (rbuf) kfree(rbuf);
|
|
if (ret) return ret;
|
|
} else dps->rdnmaxlen = 512;
|
|
dev_info(dev, " rdnmaxlen = 0x%x\n", dps->rdnmaxlen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_spi_do_read(struct dp_spi *dps, void *data, size_t len, uint8_t bpw)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
if (len > INT_MAX-4) return -EINVAL;
|
|
|
|
if (has_cmd(dps, DP_SPI_CMD_SPI_READ)) {
|
|
dev_dbg(dev, "do spi read len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPI_READ;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, 4, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do read", true, (int)len+1, (int)len+1, rbuf);
|
|
|
|
if (!ret) bufconv_from_le(data, &rbuf[1], len, bpw);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
} else if (has_cmd(dps, DP_SPI_CMD_SPIOP)) {
|
|
dev_dbg(dev, "do spiop read len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPIOP;
|
|
dps->txbuf[1] = 0;
|
|
dps->txbuf[2] = 0;
|
|
dps->txbuf[3] = 0;
|
|
dps->txbuf[4] = len & 0xff;
|
|
dps->txbuf[5] = (len >> 8) & 0xff;
|
|
dps->txbuf[6] = (len >> 16) & 0xff;
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, 7, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do spiop read", true, (int)len+1, (int)len+1, rbuf);
|
|
|
|
if (!ret) bufconv_from_le(data, &rbuf[1], len, bpw);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
} else if (has_cmd(dps, DP_SPI_CMD_SPI_RDWR)) {
|
|
dev_dbg(dev, "do rdwr read len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPI_RDWR;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
|
|
/* we need to fill the buffer with stuff bits to control the data
|
|
* that will get sent over the full duplex line. 0 is the default used
|
|
* in most places apparently? */
|
|
memset(&dps->txbuf[4], 0, len);
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, (int)len+4, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do rdwr read", true, (int)len+1, (int)len+1, rbuf);
|
|
|
|
if (!ret) bufconv_from_le(data, &rbuf[1], len, bpw);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
} else {
|
|
return -EXDEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
static int dp_spi_do_write(struct dp_spi *dps, const void *data, size_t len, uint8_t bpw)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
if (len > INT_MAX-7) return -EINVAL;
|
|
|
|
if (has_cmd(dps, DP_SPI_CMD_SPI_WRITE)) {
|
|
dev_dbg(dev, "do spi write len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPI_WRITE;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
|
|
bufconv_to_le(&dps->txbuf[4], data, len, bpw);
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, (int)len+4, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do write", true, 1, 1, rbuf);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
} else if (has_cmd(dps, DP_SPI_CMD_SPIOP)) {
|
|
dev_dbg(dev, "do spiop write len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPIOP;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
dps->txbuf[4] = 0;
|
|
dps->txbuf[5] = 0;
|
|
dps->txbuf[6] = 0;
|
|
|
|
bufconv_to_le(&dps->txbuf[7], data, len, bpw);
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, (int)len+7, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do spiop write", true, 1, 1, rbuf);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
} else if (has_cmd(dps, DP_SPI_CMD_SPI_RDWR)) {
|
|
dev_dbg(dev, "do rdwr write len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPI_RDWR;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
|
|
bufconv_to_le(&dps->txbuf[4], data, len, bpw);
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, (int)len+4, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do rdwr write", true, (int)len+1, (int)len+1, rbuf);
|
|
/* we just don't look at the returned bytes in this case */
|
|
if (rbuf) kfree(rbuf);
|
|
} else {
|
|
return -EXDEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
/* should only be called if it already has the cmd anyway (cf. spi_controller->flags) */
|
|
static int dp_spi_do_rdwr(struct dp_spi *dps, void *rdata, const void *wdata, size_t len, uint8_t bpw)
|
|
{
|
|
struct device *dev = &dps->pdev->dev;
|
|
uint8_t *rbuf;
|
|
int ret, rlen;
|
|
|
|
if (len > INT_MAX-4) return -EINVAL;
|
|
if (!has_cmd(dps, DP_SPI_CMD_SPI_RDWR)) return -EXDEV;
|
|
|
|
dev_dbg(dev, "do rdwr len=0x%zx\n", len);
|
|
|
|
dps->txbuf[0] = DP_SPI_CMD_SPI_RDWR;
|
|
dps->txbuf[1] = len & 0xff;
|
|
dps->txbuf[2] = (len >> 8) & 0xff;
|
|
dps->txbuf[3] = (len >> 16) & 0xff;
|
|
|
|
bufconv_to_le(&dps->txbuf[4], wdata, len, bpw);
|
|
|
|
ret = dp_transfer(dps->pdev, DP_CMD_MODE1_SPI, DP_XFER_FLAGS_PARSE_RESP,
|
|
dps->txbuf, (int)len+4, (void**)&rbuf, &rlen);
|
|
ret = dp_check_retval_sp(ret, rlen, dev, "do rdwr", true, (int)len+1, -1, rbuf);
|
|
|
|
if (!ret) bufconv_from_le(rdata, &rbuf[1], len, bpw);
|
|
|
|
if (rbuf) kfree(rbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int dp_spi_prepare_message(struct spi_controller *spictl, struct spi_message *msg)
|
|
{
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
struct spi_device *spidev = msg->spi;
|
|
struct device *dev = &spidev->dev;
|
|
int ret;
|
|
|
|
ret = dp_spi_set_flags(dps, spidev->chip_select,
|
|
kernmode_to_flags(dps->caps.flgcaps, spidev->mode));
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set SPI flags\n");
|
|
return ret;
|
|
}
|
|
|
|
/*ret = dp_spi_csmask_set_one(dps, spidev->chip_select);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set CS mask\n");
|
|
return ret;
|
|
}*/
|
|
|
|
return ret;
|
|
}
|
|
static void dp_spi_set_cs(struct spi_device *spidev, bool enable)
|
|
{
|
|
struct spi_controller *spictl = spidev->controller;
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
struct device *dev = &spidev->dev;
|
|
int ret;
|
|
|
|
ret = dp_spi_csmask_set_one(dps, spidev->chip_select);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set CS mask\n");
|
|
return;
|
|
}
|
|
|
|
ret = dp_spi_cs_set(dps, spidev->chip_select, enable);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set chip select line\n");
|
|
return;
|
|
}
|
|
|
|
/*return 0;*/
|
|
}
|
|
static int dp_spi_transfer_one(struct spi_controller *spictl, struct spi_device *spidev, struct spi_transfer *xfer)
|
|
{
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
struct device *dev = &spidev->dev;
|
|
int ret;
|
|
uint32_t cksize, todo, off = 0;
|
|
|
|
ret = dp_spi_set_freq(dps, 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 = dp_spi_set_bpw(dps, spidev->chip_select, xfer->bits_per_word);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to set SPI bits-per-word to %d\n", xfer->bits_per_word);
|
|
return ret;
|
|
}
|
|
|
|
if (xfer->tx_buf && xfer->rx_buf) {
|
|
cksize = dps->wrnmaxlen;
|
|
if (cksize > dps->rdnmaxlen) cksize = dps->rdnmaxlen;
|
|
} else if (xfer->tx_buf) {
|
|
cksize = dps->wrnmaxlen;
|
|
} else if (xfer->rx_buf) {
|
|
cksize = dps->rdnmaxlen;
|
|
} else return -EINVAL;
|
|
|
|
todo = xfer->len;
|
|
do {
|
|
if (todo < cksize) cksize = todo;
|
|
|
|
if (xfer->tx_buf && xfer->rx_buf) {
|
|
ret = dp_spi_do_rdwr(dps, xfer->rx_buf + off, xfer->tx_buf + off, cksize, xfer->bits_per_word);
|
|
} else if (xfer->tx_buf) {
|
|
ret = dp_spi_do_write(dps, xfer->tx_buf + off, cksize, xfer->bits_per_word);
|
|
} else /*if (xfer->rx_buf)*/ {
|
|
ret = dp_spi_do_read(dps, xfer->rx_buf + off, cksize, xfer->bits_per_word);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
dev_err(dev, "SPI transfer failed! %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
todo -= cksize;
|
|
off += cksize;
|
|
} while (todo);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_spi_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *spictl;
|
|
struct dp_spi *dps;
|
|
struct device *dev = &pdev->dev;
|
|
int ret, i;
|
|
|
|
spictl = spi_alloc_master(dev, sizeof(*dps));
|
|
if (!spictl ) return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, spictl);
|
|
|
|
dps = spi_controller_get_devdata(spictl);
|
|
|
|
dps->spictl = spictl;
|
|
dps->spictl->dev.of_node = dev->of_node;
|
|
dps->pdev = pdev;
|
|
dps->csmask = 0xff;
|
|
for (i = 0; i < 8; ++i) {
|
|
dps->devsettings[i].freq = 0;
|
|
dps->devsettings[i].flags = 0xff;
|
|
dps->devsettings[i].bpw = 0xff;
|
|
}
|
|
|
|
spin_lock_init(&dps->csmap_lock);
|
|
|
|
ret = dp_spi_check_hw(dps);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Hardware capabilities lacking\n");
|
|
goto err_free_ctl;
|
|
}
|
|
|
|
dps->txbuf = devm_kmalloc(&pdev->dev, dps->wrnmaxlen + 0x10, GFP_KERNEL);
|
|
if (!dps->txbuf) {
|
|
ret = -ENOMEM;
|
|
dev_err(dev, "No memory left for TX buffer of length 0x%x\n", dps->wrnmaxlen);
|
|
goto err_free_ctl;
|
|
}
|
|
|
|
ret = dp_spi_get_caps(dps);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to get device capabilities\n");
|
|
goto err_free_ctl;
|
|
}
|
|
|
|
spictl->min_speed_hz = dps->caps.freq_min;
|
|
spictl->max_speed_hz = dps->caps.freq_max;
|
|
spictl->bits_per_word_mask = SPI_BPW_RANGE_MASK(dps->caps.min_bpw, dps->caps.max_bpw);
|
|
spictl->num_chipselect = dps->caps.num_cs;
|
|
spictl->mode_bits = devcaps_to_kernmode(dps->caps.flgcaps);
|
|
|
|
spictl->bus_num = -1;
|
|
spictl->prepare_message = dp_spi_prepare_message;
|
|
spictl->transfer_one = dp_spi_transfer_one;
|
|
spictl->set_cs = dp_spi_set_cs;
|
|
|
|
spictl->flags = 0;
|
|
if (!has_cmd(dps, DP_SPI_CMD_SPI_RDWR))
|
|
spictl->flags |= SPI_CONTROLLER_HALF_DUPLEX;
|
|
|
|
pm_runtime_set_autosuspend_delay(dev, DP_PINST_AUTOSUSPEND_TIMEOUT);
|
|
pm_runtime_use_autosuspend(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = devm_spi_register_controller(dev, spictl);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to register SPI controller\n");
|
|
goto err_dereg;
|
|
}
|
|
|
|
dev_info(dev, "SPI bus number is %d\n", spictl->bus_num);
|
|
|
|
caps_to_binfo(dps, spictl->bus_num);
|
|
for (i = 0; i < dps->caps.num_cs; ++i) {
|
|
if (!spi_new_device(spictl, &dps->binfo[i])) {
|
|
dev_warn(dev, "failed to create %s device %d\n", dps->binfo[i].modalias, i);
|
|
}
|
|
}
|
|
|
|
return dp_spi_set_pinstate(dps, true);
|
|
|
|
err_dereg:
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
err_free_ctl:
|
|
spi_controller_put(spictl);
|
|
return ret;
|
|
}
|
|
|
|
static int dp_spi_remove(struct platform_device *pdev)
|
|
{
|
|
struct spi_controller *spictl = platform_get_drvdata(pdev);
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
dp_spi_set_pinstate(dps, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int dp_spi_suspend(struct device *dev)
|
|
{
|
|
struct spi_controller *spictl = dev_get_drvdata(dev);
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
int ret, i;
|
|
|
|
ret = spi_controller_suspend(spictl);
|
|
if (ret < 0) return ret;
|
|
|
|
dp_spi_set_pinstate(dps, false);
|
|
|
|
for (i = 0; i < 8; ++i) {
|
|
dps->devsettings[i].freq = 0;
|
|
dps->devsettings[i].flags = 0xff;
|
|
dps->devsettings[i].bpw = 0xff;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_spi_resume(struct device *dev)
|
|
{
|
|
struct spi_controller *spictl = dev_get_drvdata(dev);
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
|
|
dp_spi_set_pinstate(dps, true);
|
|
|
|
return spi_controller_resume(spictl);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
#ifdef CONFIG_PM
|
|
static int dp_spi_runtime_suspend(struct device *dev)
|
|
{
|
|
struct spi_controller *spictl = dev_get_drvdata(dev);
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
|
|
return dp_spi_set_pinstate(dps, false);
|
|
}
|
|
static int dp_spi_runtime_resume(struct device *dev)
|
|
{
|
|
struct spi_controller *spictl = dev_get_drvdata(dev);
|
|
struct dp_spi *dps = spi_controller_get_devdata(spictl);
|
|
|
|
return dp_spi_set_pinstate(dps, true);
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
static const struct dev_pm_ops dp_spi_pm = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(dp_spi_suspend, dp_spi_resume)
|
|
SET_RUNTIME_PM_OPS(dp_spi_runtime_suspend, dp_spi_runtime_resume, NULL)
|
|
};
|
|
|
|
static struct platform_driver spi_dp_driver = {
|
|
.driver = {
|
|
.name = "dragonprobe-spi",
|
|
.pm = &dp_spi_pm,
|
|
},
|
|
.probe = dp_spi_probe,
|
|
.remove = dp_spi_remove
|
|
};
|
|
module_platform_driver(spi_dp_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:dragonprobe-spi");
|