/**
 * @file fd_gpio.c
 *
 * SPI access to fine-delay internals
 *
 * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * Author: Alessandro Rubini <rubini@gnudd.com>
 * Author: Dimitris Lampridis <dimitris.lampridis@cern.ch>
 *
 * Copyright (c) 2012-2019 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "mockturtle-rt.h"

#include "wrtd-fd.h"

#define SPI_RETRIES 100

int gpio_writel(struct wrtd_fd_dev *fd, int val, int reg)
{
        int rval = fd_spi_xfer(fd, FD_CS_GPIO, 24,
                               0x4e0000 | (reg << 8) | val, NULL);

        return rval;
}

static int gpio_readl(struct wrtd_fd_dev *fd, int reg)
{
        uint32_t ret;
        int err;

        err = fd_spi_xfer(fd, FD_CS_GPIO, 24,
                          0x4f0000 | (reg << 8), &ret);

        if (err < 0)
                return err;
        return ret & 0xff;
}

static int gpio_writel_with_retry(struct wrtd_fd_dev *fd, int val, int reg)
{
        int retries = SPI_RETRIES, rv;
        while(retries--)
        {
                gpio_writel(fd, val, reg);
                rv = gpio_readl(fd, reg);
                if(rv >= 0 && (rv == val))
                {
                        if(SPI_RETRIES-1-retries > 0)
                                pr_debug("gpio_writel_with_retry: succeded after %d retries\n",
                                         SPI_RETRIES - 1 - retries);
                        return 0;
                }
        }
        return -EIO;
}

void fd_gpio_dir(struct wrtd_fd_dev *fd, int mask, int dir)
{
        fd->fd_mcp_iodir &= ~mask;
        if (dir == FD_GPIO_IN)
                fd->fd_mcp_iodir |= mask;

        gpio_writel_with_retry(fd, (fd->fd_mcp_iodir & 0xff), FD_MCP_IODIR);
        gpio_writel_with_retry(fd, (fd->fd_mcp_iodir >> 8), FD_MCP_IODIR+1);
}

void fd_gpio_val(struct wrtd_fd_dev *fd, int mask, int values)
{

        fd->fd_mcp_olat &= ~mask;
        fd->fd_mcp_olat |= values;

        gpio_writel_with_retry(fd, (fd->fd_mcp_olat & 0xff), FD_MCP_OLAT);
        gpio_writel_with_retry(fd, (fd->fd_mcp_olat >> 8), FD_MCP_OLAT+1);
}

void fd_gpio_set_clr(struct wrtd_fd_dev *fd, int mask, int set)
{
        fd_gpio_val(fd, mask, set ? mask : 0);
}

int fd_gpio_init(struct wrtd_fd_dev *fd)
{
        int i, val;

        fd->fd_mcp_iodir = 0xffff;
        fd->fd_mcp_olat = 0;

        if (gpio_writel(fd, 0x00, FD_MCP_IOCON) < 0)
                goto out;

        /* Try to read and write a register to test the SPI connection */
        for (val = 0xaa; val >= 0; val -= 0x11) {
                if (gpio_writel(fd, val, FD_MCP_IPOL) < 0)
                        goto out;
                i = gpio_readl(fd, FD_MCP_IPOL);
                if (i < 0)
                        goto out;
                if (i != val) {
                        pr_error("GPIO comm error (got 0x%x, expected 0x%x)\n", i, val);
                        return -EIO;
                }
        }
        /* last time we wrote 0, ok */
        return 0;
out:
        return -EIO;
}
