Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Greg Kroah-Hartman 2047 98.70% 1 25.00%
Linus Torvalds 21 1.01% 1 25.00%
Nathan Chancellor 3 0.14% 1 25.00%
Dan Carpenter 3 0.14% 1 25.00%
Total 2074 4


// SPDX-License-Identifier: GPL-2.0+
/*
 * KP2000 SPI controller driver
 *
 * Copyright (C) 2014-2018 Daktronics
 * Author: Matt Sickler <matt.sickler@daktronics.com>
 * Very loosely based on spi-omap2-mcspi.c
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gcd.h>
#include <linux/spi/spi.h>
#include <linux/spi/flash.h>
#include <linux/mtd/partitions.h>

#include "../kpc.h"
#include "spi_parts.h"


/***************
 * SPI Defines *
 ***************/
#define KP_SPI_REG_CONFIG 0x0 /* 0x00 */
#define KP_SPI_REG_STATUS 0x1 /* 0x08 */
#define KP_SPI_REG_FFCTRL 0x2 /* 0x10 */
#define KP_SPI_REG_TXDATA 0x3 /* 0x18 */
#define KP_SPI_REG_RXDATA 0x4 /* 0x20 */

#define KP_SPI_CLK           48000000
#define KP_SPI_MAX_FIFODEPTH 64
#define KP_SPI_MAX_FIFOWCNT  0xFFFF

#define KP_SPI_REG_CONFIG_TRM_TXRX 0
#define KP_SPI_REG_CONFIG_TRM_RX   1
#define KP_SPI_REG_CONFIG_TRM_TX   2

#define KP_SPI_REG_STATUS_RXS   0x01
#define KP_SPI_REG_STATUS_TXS   0x02
#define KP_SPI_REG_STATUS_EOT   0x04
#define KP_SPI_REG_STATUS_TXFFE 0x10
#define KP_SPI_REG_STATUS_TXFFF 0x20
#define KP_SPI_REG_STATUS_RXFFE 0x40
#define KP_SPI_REG_STATUS_RXFFF 0x80



/******************
 * SPI Structures *
 ******************/
struct kp_spi {
	struct spi_master  *master;
	u64 __iomem        *base;
	unsigned long       phys;
	struct device      *dev;
	int                 fifo_depth;
	unsigned int        pin_dir:1;
};


struct kp_spi_controller_state {
    void __iomem   *base;
    unsigned long   phys;
    unsigned char   chip_select;
    int             word_len;
    s64             conf_cache;
};


union kp_spi_config {
    /* use this to access individual elements */
    struct __attribute__((packed)) spi_config_bitfield {
        unsigned int pha       : 1; /* spim_clk Phase      */
        unsigned int pol       : 1; /* spim_clk Polarity   */
        unsigned int epol      : 1; /* spim_csx Polarity   */
        unsigned int dpe       : 1; /* Transmission Enable */
        unsigned int wl        : 5; /* Word Length         */
        unsigned int           : 3;
        unsigned int trm       : 2; /* TxRx Mode           */
        unsigned int cs        : 4; /* Chip Select         */
        unsigned int wcnt      : 7; /* Word Count          */
        unsigned int ffen      : 1; /* FIFO Enable         */
        unsigned int spi_en    : 1; /* SPI Enable          */
        unsigned int           : 5;
    } bitfield;
    /* use this to grab the whole register */
    u32 reg;
};



union kp_spi_status {
    struct __attribute__((packed)) spi_status_bitfield {
        unsigned int rx    :  1; /* Rx Status       */
        unsigned int tx    :  1; /* Tx Status       */
        unsigned int eo    :  1; /* End of Transfer */
        unsigned int       :  1;
        unsigned int txffe :  1; /* Tx FIFO Empty   */
        unsigned int txfff :  1; /* Tx FIFO Full    */
        unsigned int rxffe :  1; /* Rx FIFO Empty   */
        unsigned int rxfff :  1; /* Rx FIFO Full    */
        unsigned int       : 24;
    } bitfield;
    u32 reg;
};



union kp_spi_ffctrl {
    struct __attribute__((packed)) spi_ffctrl_bitfield {
        unsigned int ffstart :  1; /* FIFO Start */
        unsigned int         : 31;
    } bitfield;
    u32 reg;
};



/***************
 * SPI Helpers *
 ***************/
static inline int
kp_spi_bytes_per_word(int word_len)
{
    if (word_len <= 8){
        return 1;
    }
    else if (word_len <= 16) {
        return 2;
    }
    else { /* word_len <= 32 */
        return 4;
    }
}

static inline u64
kp_spi_read_reg(struct kp_spi_controller_state *cs, int idx)
{
    u64 __iomem *addr = cs->base;
    u64 val;

    addr += idx;
    if ((idx == KP_SPI_REG_CONFIG) && (cs->conf_cache >= 0)){
        return cs->conf_cache;
    }
    val = readq((void*)addr);
    return val;
}

static inline void
kp_spi_write_reg(struct kp_spi_controller_state *cs, int idx, u64 val)
{
    u64 __iomem *addr = cs->base;
    addr += idx;
    writeq(val, (void*)addr);
    if (idx == KP_SPI_REG_CONFIG)
        cs->conf_cache = val;
}

static int
kp_spi_wait_for_reg_bit(struct kp_spi_controller_state *cs, int idx, unsigned long bit)
{
    unsigned long timeout;
    timeout = jiffies + msecs_to_jiffies(1000);
    while (!(kp_spi_read_reg(cs, idx) & bit)) {
        if (time_after(jiffies, timeout)) {
            if (!(kp_spi_read_reg(cs, idx) & bit)) {
                return -ETIMEDOUT;
            } else {
                return 0;
            }
        }
        cpu_relax();
    }
    return 0;
}

static unsigned
kp_spi_txrx_pio(struct spi_device *spidev, struct spi_transfer *transfer)
{
    struct kp_spi_controller_state *cs = spidev->controller_state;
    unsigned int count = transfer->len;
    unsigned int c = count;
    
    int i;
    u8 *rx       = transfer->rx_buf;
    const u8 *tx = transfer->tx_buf;
    int processed = 0;
    
    if (tx) {
        for (i = 0 ; i < c ; i++) {
            char val = *tx++;
            
            if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_TXS) < 0) {
                goto out;
            }
            
            kp_spi_write_reg(cs, KP_SPI_REG_TXDATA, val);
            processed++;
        }
    }
    else if(rx) {
        for (i = 0 ; i < c ; i++) {
            char test=0;
            
            kp_spi_write_reg(cs, KP_SPI_REG_TXDATA, 0x00);
            
            if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_RXS) < 0) {
                goto out;
            }
            
            test = kp_spi_read_reg(cs, KP_SPI_REG_RXDATA);
            *rx++ = test;
            processed++;
        }
    }
    
    if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_EOT) < 0) {
        //TODO: Figure out how to abort transaction??  This has never happened in practice though...
    }
    
 out:
    return processed;
}

/*****************
 * SPI Functions *
 *****************/
static int
kp_spi_setup(struct spi_device *spidev)
{
    union kp_spi_config sc;
    struct kp_spi *kpspi = spi_master_get_devdata(spidev->master);
    struct kp_spi_controller_state *cs;
    
    /* setup controller state */
    cs = spidev->controller_state;
    if (!cs) {
        cs = kzalloc(sizeof(*cs), GFP_KERNEL);
        if(!cs) {
            return -ENOMEM;
        }
        cs->base = kpspi->base;
        cs->phys = kpspi->phys;
        cs->chip_select = spidev->chip_select;
        cs->word_len = spidev->bits_per_word;
        cs->conf_cache = -1;
        spidev->controller_state = cs;
    }
    
    /* set config register */
    sc.bitfield.wl = spidev->bits_per_word - 1;
    sc.bitfield.cs = spidev->chip_select;
    sc.bitfield.spi_en = 0;
    sc.bitfield.trm = 0;
    sc.bitfield.ffen = 0;
    kp_spi_write_reg(spidev->controller_state, KP_SPI_REG_CONFIG, sc.reg);
    return 0;
}

static int
kp_spi_transfer_one_message(struct spi_master *master, struct spi_message *m)
{
    struct kp_spi_controller_state *cs;
    struct spi_device   *spidev;
    struct kp_spi       *kpspi;
    struct spi_transfer *transfer;
    union kp_spi_config sc;
    int status = 0;
    
    spidev = m->spi;
    kpspi = spi_master_get_devdata(master);
    m->actual_length = 0;
    m->status = 0;
    
    cs = spidev->controller_state;
    
    /* reject invalid messages and transfers */
    if (list_empty(&m->transfers)) {
        return -EINVAL;
    }
    
    /* validate input */
    list_for_each_entry(transfer, &m->transfers, transfer_list) {
        const void *tx_buf = transfer->tx_buf;
        void       *rx_buf = transfer->rx_buf;
        unsigned    len = transfer->len;
        
        if (transfer->speed_hz > KP_SPI_CLK || (len && !(rx_buf || tx_buf))) {
            dev_dbg(kpspi->dev, "  transfer: %d Hz, %d %s%s, %d bpw\n",
                    transfer->speed_hz,
                    len,
                    tx_buf ? "tx" : "",
                    rx_buf ? "rx" : "",
                    transfer->bits_per_word);
            dev_dbg(kpspi->dev, "  transfer -EINVAL\n");
            return -EINVAL;
        }
        if (transfer->speed_hz && (transfer->speed_hz < (KP_SPI_CLK >> 15))) {
            dev_dbg(kpspi->dev, "speed_hz %d below minimum %d Hz\n",
                    transfer->speed_hz,
                    KP_SPI_CLK >> 15);
            dev_dbg(kpspi->dev, "  speed_hz -EINVAL\n");
            return -EINVAL;
        }
    }
    
    /* assert chip select to start the sequence*/
    sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
    sc.bitfield.spi_en = 1;
    kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
    
    /* work */
    if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_EOT) < 0) {
        dev_info(kpspi->dev, "EOT timed out\n");
        goto out;
    }
    
    /* do the transfers for this message */
    list_for_each_entry(transfer, &m->transfers, transfer_list) {
        if (transfer->tx_buf == NULL && transfer->rx_buf == NULL && transfer->len) {
            status = -EINVAL;
            break;
        }
        
        /* transfer */
        if (transfer->len) {
            unsigned int word_len = spidev->bits_per_word;
            unsigned count;
            
            /* set up the transfer... */
            sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
            
            /* ...direction */
            if (transfer->tx_buf) {
                sc.bitfield.trm = KP_SPI_REG_CONFIG_TRM_TX;
            }
            else if (transfer->rx_buf) {
                sc.bitfield.trm = KP_SPI_REG_CONFIG_TRM_RX;
            }
            
            /* ...word length */
            if (transfer->bits_per_word) {
                word_len = transfer->bits_per_word;
            }
            cs->word_len = word_len;
            sc.bitfield.wl = word_len-1;
            
            /* ...chip select */
            sc.bitfield.cs = spidev->chip_select;
            
            /* ...and write the new settings */
            kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
            
            /* do the transfer */
            count = kp_spi_txrx_pio(spidev, transfer);
            m->actual_length += count;
            
            if (count != transfer->len) {
                status = -EIO;
                break;
            }
        }
        
        if (transfer->delay_usecs) {
            udelay(transfer->delay_usecs);
        }
    }
    
    /* de-assert chip select to end the sequence */
    sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
    sc.bitfield.spi_en = 0;
    kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
    
 out:
    /* done work */
    spi_finalize_current_message(master);
    return 0;
}

static void
kp_spi_cleanup(struct spi_device *spidev)
{
    struct kp_spi_controller_state *cs = spidev->controller_state;
    if (cs) {
        kfree(cs);
    }
}



/******************
 * Probe / Remove *
 ******************/
static int
kp_spi_probe(struct platform_device *pldev)
{
    struct kpc_core_device_platdata *drvdata;
    struct spi_master *master;
    struct kp_spi *kpspi;
    struct resource *r;
    int status = 0;
    int i;

    drvdata = pldev->dev.platform_data;
    if (!drvdata){
        dev_err(&pldev->dev, "kp_spi_probe: platform_data is NULL!\n");
        return -ENODEV;
    }
    
    master = spi_alloc_master(&pldev->dev, sizeof(struct kp_spi));
    if (master == NULL) {
        dev_err(&pldev->dev, "kp_spi_probe: master allocation failed\n");
        return -ENOMEM;
    }
    
    /* set up the spi functions */
    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
    master->bits_per_word_mask = (unsigned int)SPI_BPW_RANGE_MASK(4, 32);
    master->setup = kp_spi_setup;
    master->transfer_one_message = kp_spi_transfer_one_message;
    master->cleanup = kp_spi_cleanup;
    
    platform_set_drvdata(pldev, master);
    
    kpspi = spi_master_get_devdata(master);
    kpspi->master = master;
    kpspi->dev = &pldev->dev;
    
    master->num_chipselect = 4;
    if (pldev->id != -1) {
        master->bus_num = pldev->id;
    }
    kpspi->pin_dir = 0;
    
    r = platform_get_resource(pldev, IORESOURCE_MEM, 0);
    if (r == NULL) {
        dev_err(&pldev->dev, "kp_spi_probe: Unable to get platform resources\n");
        status = -ENODEV;
        goto free_master;
    }
    
    kpspi->phys = (unsigned long)ioremap_nocache(r->start, resource_size(r));
    kpspi->base = (u64 __iomem *)kpspi->phys;
    
    status = spi_register_master(master);
    if (status < 0) {
        dev_err(&pldev->dev, "Unable to register SPI device\n");
        goto free_master;
    }
    
    /* register the slave boards */
    #define NEW_SPI_DEVICE_FROM_BOARD_INFO_TABLE(table) \
        for (i = 0 ; i < ARRAY_SIZE(table) ; i++) { \
            spi_new_device(master, &(table[i])); \
        }
    
    switch ((drvdata->card_id & 0xFFFF0000) >> 16){
        case PCI_DEVICE_ID_DAKTRONICS_KADOKA_P2KR0:
            NEW_SPI_DEVICE_FROM_BOARD_INFO_TABLE(p2kr0_board_info);
            break;
        default:
            dev_err(&pldev->dev, "Unknown hardware, cant know what partition table to use!\n");
            goto free_master;
            break;
    }
    
    return status;

 free_master:
    spi_master_put(master);
    return status;
}

static int
kp_spi_remove(struct platform_device *pldev)
{
    struct spi_master * master = platform_get_drvdata(pldev);
    spi_unregister_master(master);
    return 0;
}


static struct platform_driver kp_spi_driver = {
    .driver = {
        .name =     KP_DRIVER_NAME_SPI,
    },
    .probe =    kp_spi_probe,
    .remove =   kp_spi_remove,
};

module_platform_driver(kp_spi_driver);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:kp_spi");