xref: /linux/drivers/video/backlight/cgbc_bl.c (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
1*b516456eSPetri Karhula // SPDX-License-Identifier: GPL-2.0-only
2*b516456eSPetri Karhula /*
3*b516456eSPetri Karhula  * Congatec Board Controller (CGBC) Backlight Driver
4*b516456eSPetri Karhula  *
5*b516456eSPetri Karhula  * This driver provides backlight control for LCD displays connected to
6*b516456eSPetri Karhula  * Congatec boards via the CGBC (Congatec Board Controller). It integrates
7*b516456eSPetri Karhula  * with the Linux backlight subsystem and communicates with hardware through
8*b516456eSPetri Karhula  * the cgbc-core module.
9*b516456eSPetri Karhula  *
10*b516456eSPetri Karhula  * Copyright (C) 2025 Novatron Oy
11*b516456eSPetri Karhula  *
12*b516456eSPetri Karhula  * Author: Petri Karhula <petri.karhula@novatron.fi>
13*b516456eSPetri Karhula  */
14*b516456eSPetri Karhula 
15*b516456eSPetri Karhula #include <linux/backlight.h>
16*b516456eSPetri Karhula #include <linux/bitfield.h>
17*b516456eSPetri Karhula #include <linux/bits.h>
18*b516456eSPetri Karhula #include <linux/mfd/cgbc.h>
19*b516456eSPetri Karhula #include <linux/module.h>
20*b516456eSPetri Karhula #include <linux/platform_device.h>
21*b516456eSPetri Karhula 
22*b516456eSPetri Karhula #define BLT_PWM_DUTY_MASK          GENMASK(6, 0)
23*b516456eSPetri Karhula 
24*b516456eSPetri Karhula /* CGBC command for PWM brightness control*/
25*b516456eSPetri Karhula #define CGBC_CMD_BLT0_PWM          0x75
26*b516456eSPetri Karhula 
27*b516456eSPetri Karhula #define CGBC_BL_MAX_BRIGHTNESS     100
28*b516456eSPetri Karhula 
29*b516456eSPetri Karhula /**
30*b516456eSPetri Karhula  * CGBC backlight driver data
31*b516456eSPetri Karhula  * @dev: Pointer to the platform device
32*b516456eSPetri Karhula  * @cgbc: Pointer to the parent CGBC device data
33*b516456eSPetri Karhula  * @current_brightness: Current brightness level (0-100)
34*b516456eSPetri Karhula  */
35*b516456eSPetri Karhula struct cgbc_bl_data {
36*b516456eSPetri Karhula 	struct device *dev;
37*b516456eSPetri Karhula 	struct cgbc_device_data *cgbc;
38*b516456eSPetri Karhula 	unsigned int current_brightness;
39*b516456eSPetri Karhula };
40*b516456eSPetri Karhula 
41*b516456eSPetri Karhula static int cgbc_bl_read_brightness(struct cgbc_bl_data *bl_data)
42*b516456eSPetri Karhula {
43*b516456eSPetri Karhula 	u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
44*b516456eSPetri Karhula 	u8 reply_buf[3];
45*b516456eSPetri Karhula 	int ret;
46*b516456eSPetri Karhula 
47*b516456eSPetri Karhula 	ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf),
48*b516456eSPetri Karhula 			   reply_buf, sizeof(reply_buf), NULL);
49*b516456eSPetri Karhula 	if (ret < 0)
50*b516456eSPetri Karhula 		return ret;
51*b516456eSPetri Karhula 
52*b516456eSPetri Karhula 	/*
53*b516456eSPetri Karhula 	 * Get only PWM duty factor percentage,
54*b516456eSPetri Karhula 	 * ignore polarity inversion bit (bit 7)
55*b516456eSPetri Karhula 	 */
56*b516456eSPetri Karhula 	bl_data->current_brightness = FIELD_GET(BLT_PWM_DUTY_MASK, reply_buf[0]);
57*b516456eSPetri Karhula 
58*b516456eSPetri Karhula 	return 0;
59*b516456eSPetri Karhula }
60*b516456eSPetri Karhula 
61*b516456eSPetri Karhula static int cgbc_bl_update_status(struct backlight_device *bl)
62*b516456eSPetri Karhula {
63*b516456eSPetri Karhula 	struct cgbc_bl_data *bl_data = bl_get_data(bl);
64*b516456eSPetri Karhula 	u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
65*b516456eSPetri Karhula 	u8 reply_buf[3];
66*b516456eSPetri Karhula 	u8 brightness;
67*b516456eSPetri Karhula 	int ret;
68*b516456eSPetri Karhula 
69*b516456eSPetri Karhula 	brightness = backlight_get_brightness(bl);
70*b516456eSPetri Karhula 
71*b516456eSPetri Karhula 	if (brightness != bl_data->current_brightness) {
72*b516456eSPetri Karhula 		/* Read the current values */
73*b516456eSPetri Karhula 		ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
74*b516456eSPetri Karhula 				   sizeof(reply_buf), NULL);
75*b516456eSPetri Karhula 		if (ret < 0) {
76*b516456eSPetri Karhula 			dev_err(bl_data->dev, "Failed to read PWM settings: %d\n", ret);
77*b516456eSPetri Karhula 			return ret;
78*b516456eSPetri Karhula 		}
79*b516456eSPetri Karhula 
80*b516456eSPetri Karhula 		/*
81*b516456eSPetri Karhula 		 * Prepare command buffer for writing new settings. Only 2nd byte is changed
82*b516456eSPetri Karhula 		 * to set new brightness (PWM duty cycle %). Other values (polarity, frequency)
83*b516456eSPetri Karhula 		 * are preserved from the read values.
84*b516456eSPetri Karhula 		 */
85*b516456eSPetri Karhula 		cmd_buf[1] = (reply_buf[0] & ~BLT_PWM_DUTY_MASK) |
86*b516456eSPetri Karhula 			FIELD_PREP(BLT_PWM_DUTY_MASK, brightness);
87*b516456eSPetri Karhula 		cmd_buf[2] = reply_buf[1];
88*b516456eSPetri Karhula 		cmd_buf[3] = reply_buf[2];
89*b516456eSPetri Karhula 
90*b516456eSPetri Karhula 		ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
91*b516456eSPetri Karhula 				   sizeof(reply_buf), NULL);
92*b516456eSPetri Karhula 		if (ret < 0) {
93*b516456eSPetri Karhula 			dev_err(bl_data->dev, "Failed to set brightness: %d\n", ret);
94*b516456eSPetri Karhula 			return ret;
95*b516456eSPetri Karhula 		}
96*b516456eSPetri Karhula 
97*b516456eSPetri Karhula 		bl_data->current_brightness = reply_buf[0] & BLT_PWM_DUTY_MASK;
98*b516456eSPetri Karhula 
99*b516456eSPetri Karhula 		/* Verify the setting was applied correctly */
100*b516456eSPetri Karhula 		if (bl_data->current_brightness != brightness) {
101*b516456eSPetri Karhula 			dev_err(bl_data->dev,
102*b516456eSPetri Karhula 				"Brightness setting verification failed (got %u, expected %u)\n",
103*b516456eSPetri Karhula 				bl_data->current_brightness, (unsigned int)brightness);
104*b516456eSPetri Karhula 			return -EIO;
105*b516456eSPetri Karhula 		}
106*b516456eSPetri Karhula 	}
107*b516456eSPetri Karhula 
108*b516456eSPetri Karhula 	return 0;
109*b516456eSPetri Karhula }
110*b516456eSPetri Karhula 
111*b516456eSPetri Karhula static int cgbc_bl_get_brightness(struct backlight_device *bl)
112*b516456eSPetri Karhula {
113*b516456eSPetri Karhula 	struct cgbc_bl_data *bl_data = bl_get_data(bl);
114*b516456eSPetri Karhula 	int ret;
115*b516456eSPetri Karhula 
116*b516456eSPetri Karhula 	ret = cgbc_bl_read_brightness(bl_data);
117*b516456eSPetri Karhula 	if (ret < 0) {
118*b516456eSPetri Karhula 		dev_err(bl_data->dev, "Failed to read brightness: %d\n", ret);
119*b516456eSPetri Karhula 		return ret;
120*b516456eSPetri Karhula 	}
121*b516456eSPetri Karhula 
122*b516456eSPetri Karhula 	return bl_data->current_brightness;
123*b516456eSPetri Karhula }
124*b516456eSPetri Karhula 
125*b516456eSPetri Karhula static const struct backlight_ops cgbc_bl_ops = {
126*b516456eSPetri Karhula 	.options = BL_CORE_SUSPENDRESUME,
127*b516456eSPetri Karhula 	.update_status = cgbc_bl_update_status,
128*b516456eSPetri Karhula 	.get_brightness = cgbc_bl_get_brightness,
129*b516456eSPetri Karhula };
130*b516456eSPetri Karhula 
131*b516456eSPetri Karhula static int cgbc_bl_probe(struct platform_device *pdev)
132*b516456eSPetri Karhula {
133*b516456eSPetri Karhula 	struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
134*b516456eSPetri Karhula 	struct backlight_properties props = { };
135*b516456eSPetri Karhula 	struct backlight_device *bl_dev;
136*b516456eSPetri Karhula 	struct cgbc_bl_data *bl_data;
137*b516456eSPetri Karhula 	int ret;
138*b516456eSPetri Karhula 
139*b516456eSPetri Karhula 	bl_data = devm_kzalloc(&pdev->dev, sizeof(*bl_data), GFP_KERNEL);
140*b516456eSPetri Karhula 	if (!bl_data)
141*b516456eSPetri Karhula 		return -ENOMEM;
142*b516456eSPetri Karhula 
143*b516456eSPetri Karhula 	bl_data->dev = &pdev->dev;
144*b516456eSPetri Karhula 	bl_data->cgbc = cgbc;
145*b516456eSPetri Karhula 
146*b516456eSPetri Karhula 	ret = cgbc_bl_read_brightness(bl_data);
147*b516456eSPetri Karhula 	if (ret < 0)
148*b516456eSPetri Karhula 		return dev_err_probe(&pdev->dev, ret,
149*b516456eSPetri Karhula 				     "Failed to read initial brightness\n");
150*b516456eSPetri Karhula 
151*b516456eSPetri Karhula 	props.type = BACKLIGHT_PLATFORM;
152*b516456eSPetri Karhula 	props.max_brightness = CGBC_BL_MAX_BRIGHTNESS;
153*b516456eSPetri Karhula 	props.brightness = bl_data->current_brightness;
154*b516456eSPetri Karhula 	props.scale = BACKLIGHT_SCALE_LINEAR;
155*b516456eSPetri Karhula 
156*b516456eSPetri Karhula 	bl_dev = devm_backlight_device_register(&pdev->dev, "cgbc-backlight",
157*b516456eSPetri Karhula 						&pdev->dev, bl_data,
158*b516456eSPetri Karhula 						&cgbc_bl_ops, &props);
159*b516456eSPetri Karhula 	if (IS_ERR(bl_dev))
160*b516456eSPetri Karhula 		return dev_err_probe(&pdev->dev, PTR_ERR(bl_dev),
161*b516456eSPetri Karhula 			     "Failed to register backlight device\n");
162*b516456eSPetri Karhula 
163*b516456eSPetri Karhula 	platform_set_drvdata(pdev, bl_data);
164*b516456eSPetri Karhula 
165*b516456eSPetri Karhula 	return 0;
166*b516456eSPetri Karhula }
167*b516456eSPetri Karhula 
168*b516456eSPetri Karhula static struct platform_driver cgbc_bl_driver = {
169*b516456eSPetri Karhula 	.driver = {
170*b516456eSPetri Karhula 		.name = "cgbc-backlight",
171*b516456eSPetri Karhula 	},
172*b516456eSPetri Karhula 	.probe = cgbc_bl_probe,
173*b516456eSPetri Karhula };
174*b516456eSPetri Karhula 
175*b516456eSPetri Karhula module_platform_driver(cgbc_bl_driver);
176*b516456eSPetri Karhula 
177*b516456eSPetri Karhula MODULE_AUTHOR("Petri Karhula <petri.karhula@novatron.fi>");
178*b516456eSPetri Karhula MODULE_DESCRIPTION("Congatec Board Controller (CGBC) Backlight Driver");
179*b516456eSPetri Karhula MODULE_LICENSE("GPL");
180*b516456eSPetri Karhula MODULE_ALIAS("platform:cgbc-backlight");
181