162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DRM driver for Solomon SSD130X OLED displays (SPI bus)
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2022 Red Hat Inc.
662306a36Sopenharmony_ci * Authors: Javier Martinez Canillas <javierm@redhat.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci#include <linux/spi/spi.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "ssd130x.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define DRIVER_NAME	"ssd130x-spi"
1462306a36Sopenharmony_ci#define DRIVER_DESC	"DRM driver for Solomon SSD130X OLED displays (SPI)"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct ssd130x_spi_transport {
1762306a36Sopenharmony_ci	struct spi_device *spi;
1862306a36Sopenharmony_ci	struct gpio_desc *dc;
1962306a36Sopenharmony_ci};
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci * The regmap bus .write handler, it is just a wrapper around spi_write()
2362306a36Sopenharmony_ci * but toggling the Data/Command control pin (D/C#). Since for 4-wire SPI
2462306a36Sopenharmony_ci * a D/C# pin is used, in contrast with I2C where a control byte is sent,
2562306a36Sopenharmony_ci * prior to every data byte, that contains a bit with the D/C# value.
2662306a36Sopenharmony_ci *
2762306a36Sopenharmony_ci * These control bytes are considered registers by the ssd130x core driver
2862306a36Sopenharmony_ci * and can be used by the ssd130x SPI driver to determine if the data sent
2962306a36Sopenharmony_ci * is for a command register or for the Graphic Display Data RAM (GDDRAM).
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_cistatic int ssd130x_spi_write(void *context, const void *data, size_t count)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	struct ssd130x_spi_transport *t = context;
3462306a36Sopenharmony_ci	struct spi_device *spi = t->spi;
3562306a36Sopenharmony_ci	const u8 *reg = data;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (*reg == SSD130X_COMMAND)
3862306a36Sopenharmony_ci		gpiod_set_value_cansleep(t->dc, 0);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (*reg == SSD130X_DATA)
4162306a36Sopenharmony_ci		gpiod_set_value_cansleep(t->dc, 1);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* Remove control byte since is not used in a 4-wire SPI interface */
4462306a36Sopenharmony_ci	return spi_write(spi, reg + 1, count - 1);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/* The ssd130x driver does not read registers but regmap expects a .read */
4862306a36Sopenharmony_cistatic int ssd130x_spi_read(void *context, const void *reg, size_t reg_size,
4962306a36Sopenharmony_ci			    void *val, size_t val_size)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	return -EOPNOTSUPP;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic const struct regmap_config ssd130x_spi_regmap_config = {
5562306a36Sopenharmony_ci	.reg_bits = 8,
5662306a36Sopenharmony_ci	.val_bits = 8,
5762306a36Sopenharmony_ci	.write = ssd130x_spi_write,
5862306a36Sopenharmony_ci	.read = ssd130x_spi_read,
5962306a36Sopenharmony_ci	.can_multi_write = true,
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int ssd130x_spi_probe(struct spi_device *spi)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct ssd130x_spi_transport *t;
6562306a36Sopenharmony_ci	struct ssd130x_device *ssd130x;
6662306a36Sopenharmony_ci	struct regmap *regmap;
6762306a36Sopenharmony_ci	struct gpio_desc *dc;
6862306a36Sopenharmony_ci	struct device *dev = &spi->dev;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
7162306a36Sopenharmony_ci	if (IS_ERR(dc))
7262306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(dc),
7362306a36Sopenharmony_ci				     "Failed to get dc gpio\n");
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	t = devm_kzalloc(dev, sizeof(*t), GFP_KERNEL);
7662306a36Sopenharmony_ci	if (!t)
7762306a36Sopenharmony_ci		return dev_err_probe(dev, -ENOMEM,
7862306a36Sopenharmony_ci				     "Failed to allocate SPI transport data\n");
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	t->spi = spi;
8162306a36Sopenharmony_ci	t->dc = dc;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	regmap = devm_regmap_init(dev, NULL, t, &ssd130x_spi_regmap_config);
8462306a36Sopenharmony_ci	if (IS_ERR(regmap))
8562306a36Sopenharmony_ci		return PTR_ERR(regmap);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	ssd130x = ssd130x_probe(dev, regmap);
8862306a36Sopenharmony_ci	if (IS_ERR(ssd130x))
8962306a36Sopenharmony_ci		return PTR_ERR(ssd130x);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	spi_set_drvdata(spi, ssd130x);
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	return 0;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic void ssd130x_spi_remove(struct spi_device *spi)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct ssd130x_device *ssd130x = spi_get_drvdata(spi);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ssd130x_remove(ssd130x);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic void ssd130x_spi_shutdown(struct spi_device *spi)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	struct ssd130x_device *ssd130x = spi_get_drvdata(spi);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	ssd130x_shutdown(ssd130x);
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic const struct of_device_id ssd130x_of_match[] = {
11162306a36Sopenharmony_ci	{
11262306a36Sopenharmony_ci		.compatible = "sinowealth,sh1106",
11362306a36Sopenharmony_ci		.data = &ssd130x_variants[SH1106_ID],
11462306a36Sopenharmony_ci	},
11562306a36Sopenharmony_ci	{
11662306a36Sopenharmony_ci		.compatible = "solomon,ssd1305",
11762306a36Sopenharmony_ci		.data = &ssd130x_variants[SSD1305_ID],
11862306a36Sopenharmony_ci	},
11962306a36Sopenharmony_ci	{
12062306a36Sopenharmony_ci		.compatible = "solomon,ssd1306",
12162306a36Sopenharmony_ci		.data = &ssd130x_variants[SSD1306_ID],
12262306a36Sopenharmony_ci	},
12362306a36Sopenharmony_ci	{
12462306a36Sopenharmony_ci		.compatible = "solomon,ssd1307",
12562306a36Sopenharmony_ci		.data = &ssd130x_variants[SSD1307_ID],
12662306a36Sopenharmony_ci	},
12762306a36Sopenharmony_ci	{
12862306a36Sopenharmony_ci		.compatible = "solomon,ssd1309",
12962306a36Sopenharmony_ci		.data = &ssd130x_variants[SSD1309_ID],
13062306a36Sopenharmony_ci	},
13162306a36Sopenharmony_ci	{ /* sentinel */ }
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ssd130x_of_match);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci#if IS_MODULE(CONFIG_DRM_SSD130X_SPI)
13662306a36Sopenharmony_ci/*
13762306a36Sopenharmony_ci * The SPI core always reports a MODALIAS uevent of the form "spi:<dev>", even
13862306a36Sopenharmony_ci * if the device was registered via OF. This means that the module will not be
13962306a36Sopenharmony_ci * auto loaded, unless it contains an alias that matches the MODALIAS reported.
14062306a36Sopenharmony_ci *
14162306a36Sopenharmony_ci * To workaround this issue, add a SPI device ID table. Even when this should
14262306a36Sopenharmony_ci * not be needed for this driver to match the registered SPI devices.
14362306a36Sopenharmony_ci */
14462306a36Sopenharmony_cistatic const struct spi_device_id ssd130x_spi_table[] = {
14562306a36Sopenharmony_ci	{ "sh1106",  SH1106_ID },
14662306a36Sopenharmony_ci	{ "ssd1305", SSD1305_ID },
14762306a36Sopenharmony_ci	{ "ssd1306", SSD1306_ID },
14862306a36Sopenharmony_ci	{ "ssd1307", SSD1307_ID },
14962306a36Sopenharmony_ci	{ "ssd1309", SSD1309_ID },
15062306a36Sopenharmony_ci	{ /* sentinel */ }
15162306a36Sopenharmony_ci};
15262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(spi, ssd130x_spi_table);
15362306a36Sopenharmony_ci#endif
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct spi_driver ssd130x_spi_driver = {
15662306a36Sopenharmony_ci	.driver = {
15762306a36Sopenharmony_ci		.name = DRIVER_NAME,
15862306a36Sopenharmony_ci		.of_match_table = ssd130x_of_match,
15962306a36Sopenharmony_ci	},
16062306a36Sopenharmony_ci	.probe = ssd130x_spi_probe,
16162306a36Sopenharmony_ci	.remove = ssd130x_spi_remove,
16262306a36Sopenharmony_ci	.shutdown = ssd130x_spi_shutdown,
16362306a36Sopenharmony_ci};
16462306a36Sopenharmony_cimodule_spi_driver(ssd130x_spi_driver);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
16762306a36Sopenharmony_ciMODULE_AUTHOR("Javier Martinez Canillas <javierm@redhat.com>");
16862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
16962306a36Sopenharmony_ciMODULE_IMPORT_NS(DRM_SSD130X);
170