linux/drivers/spi/spi-xcomm.c
<<
>>
Prefs
   1/*
   2 * Analog Devices AD-FMCOMMS1-EBZ board I2C-SPI bridge driver
   3 *
   4 * Copyright 2012 Analog Devices Inc.
   5 * Author: Lars-Peter Clausen <lars@metafoo.de>
   6 *
   7 * Licensed under the GPL-2 or later.
   8 */
   9
  10#include <linux/kernel.h>
  11#include <linux/init.h>
  12#include <linux/module.h>
  13#include <linux/delay.h>
  14#include <linux/i2c.h>
  15#include <linux/spi/spi.h>
  16#include <asm/unaligned.h>
  17
  18#define SPI_XCOMM_SETTINGS_LEN_OFFSET           10
  19#define SPI_XCOMM_SETTINGS_3WIRE                BIT(6)
  20#define SPI_XCOMM_SETTINGS_CS_HIGH              BIT(5)
  21#define SPI_XCOMM_SETTINGS_SAMPLE_END           BIT(4)
  22#define SPI_XCOMM_SETTINGS_CPHA                 BIT(3)
  23#define SPI_XCOMM_SETTINGS_CPOL                 BIT(2)
  24#define SPI_XCOMM_SETTINGS_CLOCK_DIV_MASK       0x3
  25#define SPI_XCOMM_SETTINGS_CLOCK_DIV_64         0x2
  26#define SPI_XCOMM_SETTINGS_CLOCK_DIV_16         0x1
  27#define SPI_XCOMM_SETTINGS_CLOCK_DIV_4          0x0
  28
  29#define SPI_XCOMM_CMD_UPDATE_CONFIG     0x03
  30#define SPI_XCOMM_CMD_WRITE             0x04
  31
  32#define SPI_XCOMM_CLOCK 48000000
  33
  34struct spi_xcomm {
  35        struct i2c_client *i2c;
  36
  37        uint16_t settings;
  38        uint16_t chipselect;
  39
  40        unsigned int current_speed;
  41
  42        uint8_t buf[63];
  43};
  44
  45static int spi_xcomm_sync_config(struct spi_xcomm *spi_xcomm, unsigned int len)
  46{
  47        uint16_t settings;
  48        uint8_t *buf = spi_xcomm->buf;
  49
  50        settings = spi_xcomm->settings;
  51        settings |= len << SPI_XCOMM_SETTINGS_LEN_OFFSET;
  52
  53        buf[0] = SPI_XCOMM_CMD_UPDATE_CONFIG;
  54        put_unaligned_be16(settings, &buf[1]);
  55        put_unaligned_be16(spi_xcomm->chipselect, &buf[3]);
  56
  57        return i2c_master_send(spi_xcomm->i2c, buf, 5);
  58}
  59
  60static void spi_xcomm_chipselect(struct spi_xcomm *spi_xcomm,
  61        struct spi_device *spi, int is_active)
  62{
  63        unsigned long cs = spi->chip_select;
  64        uint16_t chipselect = spi_xcomm->chipselect;
  65
  66        if (is_active)
  67                chipselect |= BIT(cs);
  68        else
  69                chipselect &= ~BIT(cs);
  70
  71        spi_xcomm->chipselect = chipselect;
  72}
  73
  74static int spi_xcomm_setup_transfer(struct spi_xcomm *spi_xcomm,
  75        struct spi_device *spi, struct spi_transfer *t, unsigned int *settings)
  76{
  77        unsigned int speed;
  78
  79        if ((t->bits_per_word && t->bits_per_word != 8) || t->len > 62)
  80                return -EINVAL;
  81
  82        speed = t->speed_hz ? t->speed_hz : spi->max_speed_hz;
  83
  84        if (speed != spi_xcomm->current_speed) {
  85                unsigned int divider = DIV_ROUND_UP(SPI_XCOMM_CLOCK, speed);
  86                if (divider >= 64)
  87                        *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_64;
  88                else if (divider >= 16)
  89                        *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_16;
  90                else
  91                        *settings |= SPI_XCOMM_SETTINGS_CLOCK_DIV_4;
  92
  93                spi_xcomm->current_speed = speed;
  94        }
  95
  96        if (spi->mode & SPI_CPOL)
  97                *settings |= SPI_XCOMM_SETTINGS_CPOL;
  98        else
  99                *settings &= ~SPI_XCOMM_SETTINGS_CPOL;
 100
 101        if (spi->mode & SPI_CPHA)
 102                *settings &= ~SPI_XCOMM_SETTINGS_CPHA;
 103        else
 104                *settings |= SPI_XCOMM_SETTINGS_CPHA;
 105
 106        if (spi->mode & SPI_3WIRE)
 107                *settings |= SPI_XCOMM_SETTINGS_3WIRE;
 108        else
 109                *settings &= ~SPI_XCOMM_SETTINGS_3WIRE;
 110
 111        return 0;
 112}
 113
 114static int spi_xcomm_txrx_bufs(struct spi_xcomm *spi_xcomm,
 115        struct spi_device *spi, struct spi_transfer *t)
 116{
 117        int ret;
 118
 119        if (t->tx_buf) {
 120                spi_xcomm->buf[0] = SPI_XCOMM_CMD_WRITE;
 121                memcpy(spi_xcomm->buf + 1, t->tx_buf, t->len);
 122
 123                ret = i2c_master_send(spi_xcomm->i2c, spi_xcomm->buf, t->len + 1);
 124                if (ret < 0)
 125                        return ret;
 126                else if (ret != t->len + 1)
 127                        return -EIO;
 128        } else if (t->rx_buf) {
 129                ret = i2c_master_recv(spi_xcomm->i2c, t->rx_buf, t->len);
 130                if (ret < 0)
 131                        return ret;
 132                else if (ret != t->len)
 133                        return -EIO;
 134        }
 135
 136        return t->len;
 137}
 138
 139static int spi_xcomm_transfer_one(struct spi_master *master,
 140        struct spi_message *msg)
 141{
 142        struct spi_xcomm *spi_xcomm = spi_master_get_devdata(master);
 143        unsigned int settings = spi_xcomm->settings;
 144        struct spi_device *spi = msg->spi;
 145        unsigned cs_change = 0;
 146        struct spi_transfer *t;
 147        bool is_first = true;
 148        int status = 0;
 149        bool is_last;
 150
 151        is_first = true;
 152
 153        spi_xcomm_chipselect(spi_xcomm, spi, true);
 154
 155        list_for_each_entry(t, &msg->transfers, transfer_list) {
 156
 157                if (!t->tx_buf && !t->rx_buf && t->len) {
 158                        status = -EINVAL;
 159                        break;
 160                }
 161
 162                status = spi_xcomm_setup_transfer(spi_xcomm, spi, t, &settings);
 163                if (status < 0)
 164                        break;
 165
 166                is_last = list_is_last(&t->transfer_list, &msg->transfers);
 167                cs_change = t->cs_change;
 168
 169                if (cs_change ^ is_last)
 170                        settings |= BIT(5);
 171                else
 172                        settings &= ~BIT(5);
 173
 174                if (t->rx_buf) {
 175                        spi_xcomm->settings = settings;
 176                        status = spi_xcomm_sync_config(spi_xcomm, t->len);
 177                        if (status < 0)
 178                                break;
 179                } else if (settings != spi_xcomm->settings || is_first) {
 180                        spi_xcomm->settings = settings;
 181                        status = spi_xcomm_sync_config(spi_xcomm, 0);
 182                        if (status < 0)
 183                                break;
 184                }
 185
 186                if (t->len) {
 187                        status = spi_xcomm_txrx_bufs(spi_xcomm, spi, t);
 188
 189                        if (status < 0)
 190                                break;
 191
 192                        if (status > 0)
 193                                msg->actual_length += status;
 194                }
 195                status = 0;
 196
 197                if (t->delay_usecs)
 198                        udelay(t->delay_usecs);
 199
 200                is_first = false;
 201        }
 202
 203        if (status != 0 || !cs_change)
 204                spi_xcomm_chipselect(spi_xcomm, spi, false);
 205
 206        msg->status = status;
 207        spi_finalize_current_message(master);
 208
 209        return status;
 210}
 211
 212static int spi_xcomm_setup(struct spi_device *spi)
 213{
 214        if (spi->bits_per_word != 8)
 215                return -EINVAL;
 216
 217        return 0;
 218}
 219
 220static int __devinit spi_xcomm_probe(struct i2c_client *i2c,
 221        const struct i2c_device_id *id)
 222{
 223        struct spi_xcomm *spi_xcomm;
 224        struct spi_master *master;
 225        int ret;
 226
 227        master = spi_alloc_master(&i2c->dev, sizeof(*spi_xcomm));
 228        if (!master)
 229                return -ENOMEM;
 230
 231        spi_xcomm = spi_master_get_devdata(master);
 232        spi_xcomm->i2c = i2c;
 233
 234        master->num_chipselect = 16;
 235        master->mode_bits = SPI_CPHA | SPI_CPOL | SPI_3WIRE;
 236        master->flags = SPI_MASTER_HALF_DUPLEX;
 237        master->setup = spi_xcomm_setup;
 238        master->transfer_one_message = spi_xcomm_transfer_one;
 239        master->dev.of_node = i2c->dev.of_node;
 240        i2c_set_clientdata(i2c, master);
 241
 242        ret = spi_register_master(master);
 243        if (ret < 0)
 244                spi_master_put(master);
 245
 246        return ret;
 247}
 248
 249static int __devexit spi_xcomm_remove(struct i2c_client *i2c)
 250{
 251        struct spi_master *master = i2c_get_clientdata(i2c);
 252
 253        spi_unregister_master(master);
 254
 255        return 0;
 256}
 257
 258static const struct i2c_device_id spi_xcomm_ids[] = {
 259        { "spi-xcomm" },
 260        { },
 261};
 262
 263static struct i2c_driver spi_xcomm_driver = {
 264        .driver = {
 265                .name   = "spi-xcomm",
 266                .owner  = THIS_MODULE,
 267        },
 268        .id_table       = spi_xcomm_ids,
 269        .probe          = spi_xcomm_probe,
 270        .remove         = __devexit_p(spi_xcomm_remove),
 271};
 272module_i2c_driver(spi_xcomm_driver);
 273
 274MODULE_LICENSE("GPL");
 275MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 276MODULE_DESCRIPTION("Analog Devices AD-FMCOMMS1-EBZ board I2C-SPI bridge driver");
 277
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.