162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  CLPS711X SPI bus driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2012-2016 Alexander Shiyan <shc_work@mail.ru>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/clk.h>
1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/interrupt.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/regmap.h>
1662306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1762306a36Sopenharmony_ci#include <linux/mfd/syscon/clps711x.h>
1862306a36Sopenharmony_ci#include <linux/spi/spi.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define DRIVER_NAME		"clps711x-spi"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define SYNCIO_FRMLEN(x)	((x) << 8)
2362306a36Sopenharmony_ci#define SYNCIO_TXFRMEN		(1 << 14)
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistruct spi_clps711x_data {
2662306a36Sopenharmony_ci	void __iomem		*syncio;
2762306a36Sopenharmony_ci	struct regmap		*syscon;
2862306a36Sopenharmony_ci	struct clk		*spi_clk;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	u8			*tx_buf;
3162306a36Sopenharmony_ci	u8			*rx_buf;
3262306a36Sopenharmony_ci	unsigned int		bpw;
3362306a36Sopenharmony_ci	int			len;
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int spi_clps711x_prepare_message(struct spi_controller *host,
3762306a36Sopenharmony_ci					struct spi_message *msg)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct spi_clps711x_data *hw = spi_controller_get_devdata(host);
4062306a36Sopenharmony_ci	struct spi_device *spi = msg->spi;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	/* Setup mode for transfer */
4362306a36Sopenharmony_ci	return regmap_update_bits(hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCKNSEN,
4462306a36Sopenharmony_ci				  (spi->mode & SPI_CPHA) ?
4562306a36Sopenharmony_ci				  SYSCON3_ADCCKNSEN : 0);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int spi_clps711x_transfer_one(struct spi_controller *host,
4962306a36Sopenharmony_ci				     struct spi_device *spi,
5062306a36Sopenharmony_ci				     struct spi_transfer *xfer)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct spi_clps711x_data *hw = spi_controller_get_devdata(host);
5362306a36Sopenharmony_ci	u8 data;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	clk_set_rate(hw->spi_clk, xfer->speed_hz ? : spi->max_speed_hz);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	hw->len = xfer->len;
5862306a36Sopenharmony_ci	hw->bpw = xfer->bits_per_word;
5962306a36Sopenharmony_ci	hw->tx_buf = (u8 *)xfer->tx_buf;
6062306a36Sopenharmony_ci	hw->rx_buf = (u8 *)xfer->rx_buf;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	/* Initiate transfer */
6362306a36Sopenharmony_ci	data = hw->tx_buf ? *hw->tx_buf++ : 0;
6462306a36Sopenharmony_ci	writel(data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN, hw->syncio);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return 1;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic irqreturn_t spi_clps711x_isr(int irq, void *dev_id)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct spi_controller *host = dev_id;
7262306a36Sopenharmony_ci	struct spi_clps711x_data *hw = spi_controller_get_devdata(host);
7362306a36Sopenharmony_ci	u8 data;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	/* Handle RX */
7662306a36Sopenharmony_ci	data = readb(hw->syncio);
7762306a36Sopenharmony_ci	if (hw->rx_buf)
7862306a36Sopenharmony_ci		*hw->rx_buf++ = data;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* Handle TX */
8162306a36Sopenharmony_ci	if (--hw->len > 0) {
8262306a36Sopenharmony_ci		data = hw->tx_buf ? *hw->tx_buf++ : 0;
8362306a36Sopenharmony_ci		writel(data | SYNCIO_FRMLEN(hw->bpw) | SYNCIO_TXFRMEN,
8462306a36Sopenharmony_ci		       hw->syncio);
8562306a36Sopenharmony_ci	} else
8662306a36Sopenharmony_ci		spi_finalize_current_transfer(host);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return IRQ_HANDLED;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int spi_clps711x_probe(struct platform_device *pdev)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct device_node *np = pdev->dev.of_node;
9462306a36Sopenharmony_ci	struct spi_clps711x_data *hw;
9562306a36Sopenharmony_ci	struct spi_controller *host;
9662306a36Sopenharmony_ci	int irq, ret;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
9962306a36Sopenharmony_ci	if (irq < 0)
10062306a36Sopenharmony_ci		return irq;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	host = spi_alloc_host(&pdev->dev, sizeof(*hw));
10362306a36Sopenharmony_ci	if (!host)
10462306a36Sopenharmony_ci		return -ENOMEM;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	host->use_gpio_descriptors = true;
10762306a36Sopenharmony_ci	host->bus_num = -1;
10862306a36Sopenharmony_ci	host->mode_bits = SPI_CPHA | SPI_CS_HIGH;
10962306a36Sopenharmony_ci	host->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 8);
11062306a36Sopenharmony_ci	host->dev.of_node = pdev->dev.of_node;
11162306a36Sopenharmony_ci	host->prepare_message = spi_clps711x_prepare_message;
11262306a36Sopenharmony_ci	host->transfer_one = spi_clps711x_transfer_one;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	hw = spi_controller_get_devdata(host);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	hw->spi_clk = devm_clk_get(&pdev->dev, NULL);
11762306a36Sopenharmony_ci	if (IS_ERR(hw->spi_clk)) {
11862306a36Sopenharmony_ci		ret = PTR_ERR(hw->spi_clk);
11962306a36Sopenharmony_ci		goto err_out;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	hw->syscon = syscon_regmap_lookup_by_phandle(np, "syscon");
12362306a36Sopenharmony_ci	if (IS_ERR(hw->syscon)) {
12462306a36Sopenharmony_ci		ret = PTR_ERR(hw->syscon);
12562306a36Sopenharmony_ci		goto err_out;
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	hw->syncio = devm_platform_ioremap_resource(pdev, 0);
12962306a36Sopenharmony_ci	if (IS_ERR(hw->syncio)) {
13062306a36Sopenharmony_ci		ret = PTR_ERR(hw->syncio);
13162306a36Sopenharmony_ci		goto err_out;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* Disable extended mode due hardware problems */
13562306a36Sopenharmony_ci	regmap_update_bits(hw->syscon, SYSCON_OFFSET, SYSCON3_ADCCON, 0);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Clear possible pending interrupt */
13862306a36Sopenharmony_ci	readl(hw->syncio);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, irq, spi_clps711x_isr, 0,
14162306a36Sopenharmony_ci			       dev_name(&pdev->dev), host);
14262306a36Sopenharmony_ci	if (ret)
14362306a36Sopenharmony_ci		goto err_out;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	ret = devm_spi_register_controller(&pdev->dev, host);
14662306a36Sopenharmony_ci	if (!ret)
14762306a36Sopenharmony_ci		return 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cierr_out:
15062306a36Sopenharmony_ci	spi_controller_put(host);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return ret;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic const struct of_device_id clps711x_spi_dt_ids[] = {
15662306a36Sopenharmony_ci	{ .compatible = "cirrus,ep7209-spi", },
15762306a36Sopenharmony_ci	{ }
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, clps711x_spi_dt_ids);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic struct platform_driver clps711x_spi_driver = {
16262306a36Sopenharmony_ci	.driver	= {
16362306a36Sopenharmony_ci		.name	= DRIVER_NAME,
16462306a36Sopenharmony_ci		.of_match_table = clps711x_spi_dt_ids,
16562306a36Sopenharmony_ci	},
16662306a36Sopenharmony_ci	.probe	= spi_clps711x_probe,
16762306a36Sopenharmony_ci};
16862306a36Sopenharmony_cimodule_platform_driver(clps711x_spi_driver);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
17162306a36Sopenharmony_ciMODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
17262306a36Sopenharmony_ciMODULE_DESCRIPTION("CLPS711X SPI bus driver");
17362306a36Sopenharmony_ciMODULE_ALIAS("platform:" DRIVER_NAME);
174