xref: /linux/drivers/auxdisplay/seg-led-gpio.c (revision e7073830cc8b52ef3df7dd150e4dac7706e0e104)
1899383f9SChris Packham // SPDX-License-Identifier: GPL-2.0
2899383f9SChris Packham /*
3899383f9SChris Packham  * Driver for a 7-segment LED display
4899383f9SChris Packham  *
5899383f9SChris Packham  * The decimal point LED present on some devices is currently not
6899383f9SChris Packham  * supported.
7899383f9SChris Packham  *
8899383f9SChris Packham  * Copyright (C) Allied Telesis Labs
9899383f9SChris Packham  */
10899383f9SChris Packham 
11899383f9SChris Packham #include <linux/bitmap.h>
12899383f9SChris Packham #include <linux/container_of.h>
13899383f9SChris Packham #include <linux/errno.h>
14899383f9SChris Packham #include <linux/gpio/consumer.h>
15899383f9SChris Packham #include <linux/map_to_7segment.h>
16899383f9SChris Packham #include <linux/mod_devicetable.h>
17899383f9SChris Packham #include <linux/module.h>
18899383f9SChris Packham #include <linux/platform_device.h>
19899383f9SChris Packham #include <linux/types.h>
20899383f9SChris Packham #include <linux/workqueue.h>
21899383f9SChris Packham 
22899383f9SChris Packham #include "line-display.h"
23899383f9SChris Packham 
24899383f9SChris Packham struct seg_led_priv {
25899383f9SChris Packham 	struct linedisp linedisp;
26899383f9SChris Packham 	struct delayed_work work;
27899383f9SChris Packham 	struct gpio_descs *segment_gpios;
28899383f9SChris Packham };
29899383f9SChris Packham 
seg_led_update(struct work_struct * work)30899383f9SChris Packham static void seg_led_update(struct work_struct *work)
31899383f9SChris Packham {
32899383f9SChris Packham 	struct seg_led_priv *priv = container_of(work, struct seg_led_priv, work.work);
33899383f9SChris Packham 	struct linedisp *linedisp = &priv->linedisp;
34899383f9SChris Packham 	struct linedisp_map *map = linedisp->map;
35899383f9SChris Packham 	DECLARE_BITMAP(values, 8) = { };
36899383f9SChris Packham 
37899383f9SChris Packham 	bitmap_set_value8(values, map_to_seg7(&map->map.seg7, linedisp->buf[0]), 0);
38899383f9SChris Packham 
39899383f9SChris Packham 	gpiod_set_array_value_cansleep(priv->segment_gpios->ndescs, priv->segment_gpios->desc,
40899383f9SChris Packham 				       priv->segment_gpios->info, values);
41899383f9SChris Packham }
42899383f9SChris Packham 
seg_led_linedisp_get_map_type(struct linedisp * linedisp)43899383f9SChris Packham static int seg_led_linedisp_get_map_type(struct linedisp *linedisp)
44899383f9SChris Packham {
45899383f9SChris Packham 	struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp);
46899383f9SChris Packham 
47899383f9SChris Packham 	INIT_DELAYED_WORK(&priv->work, seg_led_update);
48899383f9SChris Packham 	return LINEDISP_MAP_SEG7;
49899383f9SChris Packham }
50899383f9SChris Packham 
seg_led_linedisp_update(struct linedisp * linedisp)51899383f9SChris Packham static void seg_led_linedisp_update(struct linedisp *linedisp)
52899383f9SChris Packham {
53899383f9SChris Packham 	struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp);
54899383f9SChris Packham 
55899383f9SChris Packham 	schedule_delayed_work(&priv->work, 0);
56899383f9SChris Packham }
57899383f9SChris Packham 
58899383f9SChris Packham static const struct linedisp_ops seg_led_linedisp_ops = {
59899383f9SChris Packham 	.get_map_type = seg_led_linedisp_get_map_type,
60899383f9SChris Packham 	.update = seg_led_linedisp_update,
61899383f9SChris Packham };
62899383f9SChris Packham 
seg_led_probe(struct platform_device * pdev)63899383f9SChris Packham static int seg_led_probe(struct platform_device *pdev)
64899383f9SChris Packham {
65899383f9SChris Packham 	struct seg_led_priv *priv;
66899383f9SChris Packham 	struct device *dev = &pdev->dev;
67899383f9SChris Packham 
68899383f9SChris Packham 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
69899383f9SChris Packham 	if (!priv)
70899383f9SChris Packham 		return -ENOMEM;
71899383f9SChris Packham 
72899383f9SChris Packham 	platform_set_drvdata(pdev, priv);
73899383f9SChris Packham 
74899383f9SChris Packham 	priv->segment_gpios = devm_gpiod_get_array(dev, "segment", GPIOD_OUT_LOW);
75899383f9SChris Packham 	if (IS_ERR(priv->segment_gpios))
76899383f9SChris Packham 		return PTR_ERR(priv->segment_gpios);
77899383f9SChris Packham 
78899383f9SChris Packham 	if (priv->segment_gpios->ndescs < 7 || priv->segment_gpios->ndescs > 8)
79899383f9SChris Packham 		return -EINVAL;
80899383f9SChris Packham 
81899383f9SChris Packham 	return linedisp_register(&priv->linedisp, dev, 1, &seg_led_linedisp_ops);
82899383f9SChris Packham }
83899383f9SChris Packham 
seg_led_remove(struct platform_device * pdev)84*c352a041SUwe Kleine-König static void seg_led_remove(struct platform_device *pdev)
85899383f9SChris Packham {
86899383f9SChris Packham 	struct seg_led_priv *priv = platform_get_drvdata(pdev);
87899383f9SChris Packham 
88899383f9SChris Packham 	cancel_delayed_work_sync(&priv->work);
89899383f9SChris Packham 	linedisp_unregister(&priv->linedisp);
90899383f9SChris Packham }
91899383f9SChris Packham 
92899383f9SChris Packham static const struct of_device_id seg_led_of_match[] = {
93899383f9SChris Packham 	{ .compatible = "gpio-7-segment"},
94899383f9SChris Packham 	{}
95899383f9SChris Packham };
96899383f9SChris Packham MODULE_DEVICE_TABLE(of, seg_led_of_match);
97899383f9SChris Packham 
98899383f9SChris Packham static struct platform_driver seg_led_driver = {
99899383f9SChris Packham 	.probe = seg_led_probe,
100*c352a041SUwe Kleine-König 	.remove_new = seg_led_remove,
101899383f9SChris Packham 	.driver = {
102899383f9SChris Packham 		.name = "seg-led-gpio",
103899383f9SChris Packham 		.of_match_table = seg_led_of_match,
104899383f9SChris Packham 	},
105899383f9SChris Packham };
106899383f9SChris Packham module_platform_driver(seg_led_driver);
107899383f9SChris Packham 
108899383f9SChris Packham MODULE_AUTHOR("Chris Packham <chris.packham@alliedtelesis.co.nz>");
109899383f9SChris Packham MODULE_DESCRIPTION("7 segment LED driver");
110899383f9SChris Packham MODULE_LICENSE("GPL");
111d8abf9d4SChris Packham MODULE_IMPORT_NS(LINEDISP);
112