root/drivers/spi/spi-lp8841-rtc.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * SPI host driver for ICP DAS LP-8841 RTC
 *
 * Copyright (C) 2016 Sergei Ianovich
 *
 * based on
 *
 * Dallas DS1302 RTC Support
 * Copyright (C) 2002 David McCullough
 * Copyright (C) 2003 - 2007 Paul Mundt
 */
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/spi/spi.h>

#define DRIVER_NAME     "spi_lp8841_rtc"

#define SPI_LP8841_RTC_CE       0x01
#define SPI_LP8841_RTC_CLK      0x02
#define SPI_LP8841_RTC_nWE      0x04
#define SPI_LP8841_RTC_MOSI     0x08
#define SPI_LP8841_RTC_MISO     0x01

/*
 * REVISIT If there is support for SPI_3WIRE and SPI_LSB_FIRST in SPI
 * GPIO driver, this SPI driver can be replaced by a simple GPIO driver
 * providing 3 GPIO pins.
 */

struct spi_lp8841_rtc {
        void            *iomem;
        unsigned long   state;
};

static inline void
setsck(struct spi_lp8841_rtc *data, int is_on)
{
        if (is_on)
                data->state |= SPI_LP8841_RTC_CLK;
        else
                data->state &= ~SPI_LP8841_RTC_CLK;
        writeb(data->state, data->iomem);
}

static inline void
setmosi(struct spi_lp8841_rtc *data, int is_on)
{
        if (is_on)
                data->state |= SPI_LP8841_RTC_MOSI;
        else
                data->state &= ~SPI_LP8841_RTC_MOSI;
        writeb(data->state, data->iomem);
}

static inline int
getmiso(struct spi_lp8841_rtc *data)
{
        return ioread8(data->iomem) & SPI_LP8841_RTC_MISO;
}

static inline u32
bitbang_txrx_be_cpha0_lsb(struct spi_lp8841_rtc *data,
                unsigned usecs, unsigned cpol, unsigned flags,
                u32 word, u8 bits)
{
        /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */

        u32 shift = 32 - bits;
        /* clock starts at inactive polarity */
        for (; likely(bits); bits--) {

                /* setup LSB (to target) on leading edge */
                if ((flags & SPI_CONTROLLER_NO_TX) == 0)
                        setmosi(data, (word & 1));

                usleep_range(usecs, usecs + 1); /* T(setup) */

                /* sample LSB (from target) on trailing edge */
                word >>= 1;
                if ((flags & SPI_CONTROLLER_NO_RX) == 0)
                        word |= (getmiso(data) << 31);

                setsck(data, !cpol);
                usleep_range(usecs, usecs + 1);

                setsck(data, cpol);
        }

        word >>= shift;
        return word;
}

static int
spi_lp8841_rtc_transfer_one(struct spi_controller *host,
                            struct spi_device *spi,
                            struct spi_transfer *t)
{
        struct spi_lp8841_rtc   *data = spi_controller_get_devdata(host);
        unsigned                count = t->len;
        const u8                *tx = t->tx_buf;
        u8                      *rx = t->rx_buf;
        u8                      word = 0;
        int                     ret = 0;

        if (tx) {
                data->state &= ~SPI_LP8841_RTC_nWE;
                writeb(data->state, data->iomem);
                while (likely(count > 0)) {
                        word = *tx++;
                        bitbang_txrx_be_cpha0_lsb(data, 1, 0,
                                        SPI_CONTROLLER_NO_RX, word, 8);
                        count--;
                }
        } else if (rx) {
                data->state |= SPI_LP8841_RTC_nWE;
                writeb(data->state, data->iomem);
                while (likely(count > 0)) {
                        word = bitbang_txrx_be_cpha0_lsb(data, 1, 0,
                                        SPI_CONTROLLER_NO_TX, word, 8);
                        *rx++ = word;
                        count--;
                }
        } else {
                ret = -EINVAL;
        }

        spi_finalize_current_transfer(host);

        return ret;
}

static void
spi_lp8841_rtc_set_cs(struct spi_device *spi, bool enable)
{
        struct spi_lp8841_rtc *data = spi_controller_get_devdata(spi->controller);

        data->state = 0;
        writeb(data->state, data->iomem);
        if (enable) {
                usleep_range(4, 5);
                data->state |= SPI_LP8841_RTC_CE;
                writeb(data->state, data->iomem);
                usleep_range(4, 5);
        }
}

static int
spi_lp8841_rtc_setup(struct spi_device *spi)
{
        if ((spi->mode & SPI_CS_HIGH) == 0) {
                dev_err(&spi->dev, "unsupported active low chip select\n");
                return -EINVAL;
        }

        if ((spi->mode & SPI_LSB_FIRST) == 0) {
                dev_err(&spi->dev, "unsupported MSB first mode\n");
                return -EINVAL;
        }

        if ((spi->mode & SPI_3WIRE) == 0) {
                dev_err(&spi->dev, "unsupported wiring. 3 wires required\n");
                return -EINVAL;
        }

        return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id spi_lp8841_rtc_dt_ids[] = {
        { .compatible = "icpdas,lp8841-spi-rtc" },
        { }
};

MODULE_DEVICE_TABLE(of, spi_lp8841_rtc_dt_ids);
#endif

static int
spi_lp8841_rtc_probe(struct platform_device *pdev)
{
        int                             ret;
        struct spi_controller           *host;
        struct spi_lp8841_rtc           *data;

        host = spi_alloc_host(&pdev->dev, sizeof(*data));
        if (!host)
                return -ENOMEM;
        platform_set_drvdata(pdev, host);

        host->flags = SPI_CONTROLLER_HALF_DUPLEX;
        host->mode_bits = SPI_CS_HIGH | SPI_3WIRE | SPI_LSB_FIRST;

        host->bus_num = pdev->id;
        host->num_chipselect = 1;
        host->setup = spi_lp8841_rtc_setup;
        host->set_cs = spi_lp8841_rtc_set_cs;
        host->transfer_one = spi_lp8841_rtc_transfer_one;
        host->bits_per_word_mask = SPI_BPW_MASK(8);
#ifdef CONFIG_OF
#endif

        data = spi_controller_get_devdata(host);

        data->iomem = devm_platform_ioremap_resource(pdev, 0);
        ret = PTR_ERR_OR_ZERO(data->iomem);
        if (ret) {
                dev_err(&pdev->dev, "failed to get IO address\n");
                goto err_put_host;
        }

        /* register with the SPI framework */
        ret = devm_spi_register_controller(&pdev->dev, host);
        if (ret) {
                dev_err(&pdev->dev, "cannot register spi host\n");
                goto err_put_host;
        }

        return ret;


err_put_host:
        spi_controller_put(host);

        return ret;
}

MODULE_ALIAS("platform:" DRIVER_NAME);

static struct platform_driver spi_lp8841_rtc_driver = {
        .driver = {
                .name   = DRIVER_NAME,
                .of_match_table = of_match_ptr(spi_lp8841_rtc_dt_ids),
        },
        .probe          = spi_lp8841_rtc_probe,
};
module_platform_driver(spi_lp8841_rtc_driver);

MODULE_DESCRIPTION("SPI host driver for ICP DAS LP-8841 RTC");
MODULE_AUTHOR("Sergei Ianovich");
MODULE_LICENSE("GPL");