linux/drivers/bus/ts-nbus.c
<<
>>
Prefs
   1/*
   2 * NBUS driver for TS-4600 based boards
   3 *
   4 * Copyright (c) 2016 - Savoir-faire Linux
   5 * Author: Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com>
   6 *
   7 * This file is licensed under the terms of the GNU General Public
   8 * License version 2. This program is licensed "as is" without any
   9 * warranty of any kind, whether express or implied.
  10 *
  11 * This driver implements a GPIOs bit-banged bus, called the NBUS by Technologic
  12 * Systems. It is used to communicate with the peripherals in the FPGA on the
  13 * TS-4600 SoM.
  14 */
  15
  16#include <linux/bitops.h>
  17#include <linux/gpio/consumer.h>
  18#include <linux/kernel.h>
  19#include <linux/module.h>
  20#include <linux/mutex.h>
  21#include <linux/of_platform.h>
  22#include <linux/platform_device.h>
  23#include <linux/pwm.h>
  24#include <linux/ts-nbus.h>
  25
  26#define TS_NBUS_DIRECTION_IN  0
  27#define TS_NBUS_DIRECTION_OUT 1
  28#define TS_NBUS_WRITE_ADR 0
  29#define TS_NBUS_WRITE_VAL 1
  30
  31struct ts_nbus {
  32        struct pwm_device *pwm;
  33        struct gpio_descs *data;
  34        struct gpio_desc *csn;
  35        struct gpio_desc *txrx;
  36        struct gpio_desc *strobe;
  37        struct gpio_desc *ale;
  38        struct gpio_desc *rdy;
  39        struct mutex lock;
  40};
  41
  42/*
  43 * request all gpios required by the bus.
  44 */
  45static int ts_nbus_init_pdata(struct platform_device *pdev, struct ts_nbus
  46                *ts_nbus)
  47{
  48        ts_nbus->data = devm_gpiod_get_array(&pdev->dev, "ts,data",
  49                        GPIOD_OUT_HIGH);
  50        if (IS_ERR(ts_nbus->data)) {
  51                dev_err(&pdev->dev, "failed to retrieve ts,data-gpio from dts\n");
  52                return PTR_ERR(ts_nbus->data);
  53        }
  54
  55        ts_nbus->csn = devm_gpiod_get(&pdev->dev, "ts,csn", GPIOD_OUT_HIGH);
  56        if (IS_ERR(ts_nbus->csn)) {
  57                dev_err(&pdev->dev, "failed to retrieve ts,csn-gpio from dts\n");
  58                return PTR_ERR(ts_nbus->csn);
  59        }
  60
  61        ts_nbus->txrx = devm_gpiod_get(&pdev->dev, "ts,txrx", GPIOD_OUT_HIGH);
  62        if (IS_ERR(ts_nbus->txrx)) {
  63                dev_err(&pdev->dev, "failed to retrieve ts,txrx-gpio from dts\n");
  64                return PTR_ERR(ts_nbus->txrx);
  65        }
  66
  67        ts_nbus->strobe = devm_gpiod_get(&pdev->dev, "ts,strobe", GPIOD_OUT_HIGH);
  68        if (IS_ERR(ts_nbus->strobe)) {
  69                dev_err(&pdev->dev, "failed to retrieve ts,strobe-gpio from dts\n");
  70                return PTR_ERR(ts_nbus->strobe);
  71        }
  72
  73        ts_nbus->ale = devm_gpiod_get(&pdev->dev, "ts,ale", GPIOD_OUT_HIGH);
  74        if (IS_ERR(ts_nbus->ale)) {
  75                dev_err(&pdev->dev, "failed to retrieve ts,ale-gpio from dts\n");
  76                return PTR_ERR(ts_nbus->ale);
  77        }
  78
  79        ts_nbus->rdy = devm_gpiod_get(&pdev->dev, "ts,rdy", GPIOD_IN);
  80        if (IS_ERR(ts_nbus->rdy)) {
  81                dev_err(&pdev->dev, "failed to retrieve ts,rdy-gpio from dts\n");
  82                return PTR_ERR(ts_nbus->rdy);
  83        }
  84
  85        return 0;
  86}
  87
  88/*
  89 * the data gpios are used for reading and writing values, their directions
  90 * should be adjusted accordingly.
  91 */
  92static void ts_nbus_set_direction(struct ts_nbus *ts_nbus, int direction)
  93{
  94        int i;
  95
  96        for (i = 0; i < 8; i++) {
  97                if (direction == TS_NBUS_DIRECTION_IN)
  98                        gpiod_direction_input(ts_nbus->data->desc[i]);
  99                else
 100                        /* when used as output the default state of the data
 101                         * lines are set to high */
 102                        gpiod_direction_output(ts_nbus->data->desc[i], 1);
 103        }
 104}
 105
 106/*
 107 * reset the bus in its initial state.
 108 * The data, csn, strobe and ale lines must be zero'ed to let the FPGA knows a
 109 * new transaction can be process.
 110 */
 111static void ts_nbus_reset_bus(struct ts_nbus *ts_nbus)
 112{
 113        DECLARE_BITMAP(values, 8);
 114
 115        values[0] = 0;
 116
 117        gpiod_set_array_value_cansleep(8, ts_nbus->data->desc,
 118                                       ts_nbus->data->info, values);
 119        gpiod_set_value_cansleep(ts_nbus->csn, 0);
 120        gpiod_set_value_cansleep(ts_nbus->strobe, 0);
 121        gpiod_set_value_cansleep(ts_nbus->ale, 0);
 122}
 123
 124/*
 125 * let the FPGA knows it can process.
 126 */
 127static void ts_nbus_start_transaction(struct ts_nbus *ts_nbus)
 128{
 129        gpiod_set_value_cansleep(ts_nbus->strobe, 1);
 130}
 131
 132/*
 133 * read a byte value from the data gpios.
 134 * return 0 on success or negative errno on failure.
 135 */
 136static int ts_nbus_read_byte(struct ts_nbus *ts_nbus, u8 *val)
 137{
 138        struct gpio_descs *gpios = ts_nbus->data;
 139        int ret, i;
 140
 141        *val = 0;
 142        for (i = 0; i < 8; i++) {
 143                ret = gpiod_get_value_cansleep(gpios->desc[i]);
 144                if (ret < 0)
 145                        return ret;
 146                if (ret)
 147                        *val |= BIT(i);
 148        }
 149
 150        return 0;
 151}
 152
 153/*
 154 * set the data gpios accordingly to the byte value.
 155 */
 156static void ts_nbus_write_byte(struct ts_nbus *ts_nbus, u8 byte)
 157{
 158        struct gpio_descs *gpios = ts_nbus->data;
 159        DECLARE_BITMAP(values, 8);
 160
 161        values[0] = byte;
 162
 163        gpiod_set_array_value_cansleep(8, gpios->desc, gpios->info, values);
 164}
 165
 166/*
 167 * reading the bus consists of resetting the bus, then notifying the FPGA to
 168 * send the data in the data gpios and return the read value.
 169 * return 0 on success or negative errno on failure.
 170 */
 171static int ts_nbus_read_bus(struct ts_nbus *ts_nbus, u8 *val)
 172{
 173        ts_nbus_reset_bus(ts_nbus);
 174        ts_nbus_start_transaction(ts_nbus);
 175
 176        return ts_nbus_read_byte(ts_nbus, val);
 177}
 178
 179/*
 180 * writing to the bus consists of resetting the bus, then define the type of
 181 * command (address/value), write the data and notify the FPGA to retrieve the
 182 * value in the data gpios.
 183 */
 184static void ts_nbus_write_bus(struct ts_nbus *ts_nbus, int cmd, u8 val)
 185{
 186        ts_nbus_reset_bus(ts_nbus);
 187
 188        if (cmd == TS_NBUS_WRITE_ADR)
 189                gpiod_set_value_cansleep(ts_nbus->ale, 1);
 190
 191        ts_nbus_write_byte(ts_nbus, val);
 192        ts_nbus_start_transaction(ts_nbus);
 193}
 194
 195/*
 196 * read the value in the FPGA register at the given address.
 197 * return 0 on success or negative errno on failure.
 198 */
 199int ts_nbus_read(struct ts_nbus *ts_nbus, u8 adr, u16 *val)
 200{
 201        int ret, i;
 202        u8 byte;
 203
 204        /* bus access must be atomic */
 205        mutex_lock(&ts_nbus->lock);
 206
 207        /* set the bus in read mode */
 208        gpiod_set_value_cansleep(ts_nbus->txrx, 0);
 209
 210        /* write address */
 211        ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_ADR, adr);
 212
 213        /* set the data gpios direction as input before reading */
 214        ts_nbus_set_direction(ts_nbus, TS_NBUS_DIRECTION_IN);
 215
 216        /* reading value MSB first */
 217        do {
 218                *val = 0;
 219                byte = 0;
 220                for (i = 1; i >= 0; i--) {
 221                        /* read a byte from the bus, leave on error */
 222                        ret = ts_nbus_read_bus(ts_nbus, &byte);
 223                        if (ret < 0)
 224                                goto err;
 225
 226                        /* append the byte read to the final value */
 227                        *val |= byte << (i * 8);
 228                }
 229                gpiod_set_value_cansleep(ts_nbus->csn, 1);
 230                ret = gpiod_get_value_cansleep(ts_nbus->rdy);
 231        } while (ret);
 232
 233err:
 234        /* restore the data gpios direction as output after reading */
 235        ts_nbus_set_direction(ts_nbus, TS_NBUS_DIRECTION_OUT);
 236
 237        mutex_unlock(&ts_nbus->lock);
 238
 239        return ret;
 240}
 241EXPORT_SYMBOL_GPL(ts_nbus_read);
 242
 243/*
 244 * write the desired value in the FPGA register at the given address.
 245 */
 246int ts_nbus_write(struct ts_nbus *ts_nbus, u8 adr, u16 val)
 247{
 248        int i;
 249
 250        /* bus access must be atomic */
 251        mutex_lock(&ts_nbus->lock);
 252
 253        /* set the bus in write mode */
 254        gpiod_set_value_cansleep(ts_nbus->txrx, 1);
 255
 256        /* write address */
 257        ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_ADR, adr);
 258
 259        /* writing value MSB first */
 260        for (i = 1; i >= 0; i--)
 261                ts_nbus_write_bus(ts_nbus, TS_NBUS_WRITE_VAL, (u8)(val >> (i * 8)));
 262
 263        /* wait for completion */
 264        gpiod_set_value_cansleep(ts_nbus->csn, 1);
 265        while (gpiod_get_value_cansleep(ts_nbus->rdy) != 0) {
 266                gpiod_set_value_cansleep(ts_nbus->csn, 0);
 267                gpiod_set_value_cansleep(ts_nbus->csn, 1);
 268        }
 269
 270        mutex_unlock(&ts_nbus->lock);
 271
 272        return 0;
 273}
 274EXPORT_SYMBOL_GPL(ts_nbus_write);
 275
 276static int ts_nbus_probe(struct platform_device *pdev)
 277{
 278        struct pwm_device *pwm;
 279        struct pwm_args pargs;
 280        struct device *dev = &pdev->dev;
 281        struct ts_nbus *ts_nbus;
 282        int ret;
 283
 284        ts_nbus = devm_kzalloc(dev, sizeof(*ts_nbus), GFP_KERNEL);
 285        if (!ts_nbus)
 286                return -ENOMEM;
 287
 288        mutex_init(&ts_nbus->lock);
 289
 290        ret = ts_nbus_init_pdata(pdev, ts_nbus);
 291        if (ret < 0)
 292                return ret;
 293
 294        pwm = devm_pwm_get(dev, NULL);
 295        if (IS_ERR(pwm)) {
 296                ret = PTR_ERR(pwm);
 297                if (ret != -EPROBE_DEFER)
 298                        dev_err(dev, "unable to request PWM\n");
 299                return ret;
 300        }
 301
 302        pwm_get_args(pwm, &pargs);
 303        if (!pargs.period) {
 304                dev_err(&pdev->dev, "invalid PWM period\n");
 305                return -EINVAL;
 306        }
 307
 308        /*
 309         * FIXME: pwm_apply_args() should be removed when switching to
 310         * the atomic PWM API.
 311         */
 312        pwm_apply_args(pwm);
 313        ret = pwm_config(pwm, pargs.period, pargs.period);
 314        if (ret < 0)
 315                return ret;
 316
 317        /*
 318         * we can now start the FPGA and populate the peripherals.
 319         */
 320        pwm_enable(pwm);
 321        ts_nbus->pwm = pwm;
 322
 323        /*
 324         * let the child nodes retrieve this instance of the ts-nbus.
 325         */
 326        dev_set_drvdata(dev, ts_nbus);
 327
 328        ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
 329        if (ret < 0)
 330                return ret;
 331
 332        dev_info(dev, "initialized\n");
 333
 334        return 0;
 335}
 336
 337static int ts_nbus_remove(struct platform_device *pdev)
 338{
 339        struct ts_nbus *ts_nbus = dev_get_drvdata(&pdev->dev);
 340
 341        /* shutdown the FPGA */
 342        mutex_lock(&ts_nbus->lock);
 343        pwm_disable(ts_nbus->pwm);
 344        mutex_unlock(&ts_nbus->lock);
 345
 346        return 0;
 347}
 348
 349static const struct of_device_id ts_nbus_of_match[] = {
 350        { .compatible = "technologic,ts-nbus", },
 351        { },
 352};
 353MODULE_DEVICE_TABLE(of, ts_nbus_of_match);
 354
 355static struct platform_driver ts_nbus_driver = {
 356        .probe          = ts_nbus_probe,
 357        .remove         = ts_nbus_remove,
 358        .driver         = {
 359                .name   = "ts_nbus",
 360                .of_match_table = ts_nbus_of_match,
 361        },
 362};
 363
 364module_platform_driver(ts_nbus_driver);
 365
 366MODULE_ALIAS("platform:ts_nbus");
 367MODULE_AUTHOR("Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com>");
 368MODULE_DESCRIPTION("Technologic Systems NBUS");
 369MODULE_LICENSE("GPL v2");
 370