1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) Intel Corp. 2007.
4  * All Rights Reserved.
5  *
6  * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
7  * develop this driver.
8  *
9  * This file is part of the Carillo Ranch video subsystem driver.
10  *
11  * Authors:
12  *   Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
13  *   Alan Hourihane <alanh-at-tungstengraphics-dot-com>
14  */
15 
16 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
17 
18 #include <linux/module.h>
19 #include <linux/kernel.h>
20 #include <linux/init.h>
21 #include <linux/platform_device.h>
22 #include <linux/mutex.h>
23 #include <linux/fb.h>
24 #include <linux/backlight.h>
25 #include <linux/lcd.h>
26 #include <linux/pci.h>
27 #include <linux/slab.h>
28 
29 /* The LVDS- and panel power controls sits on the
30  * GPIO port of the ISA bridge.
31  */
32 
33 #define CRVML_DEVICE_LPC    0x27B8
34 #define CRVML_REG_GPIOBAR   0x48
35 #define CRVML_REG_GPIOEN    0x4C
36 #define CRVML_GPIOEN_BIT    (1 << 4)
37 #define CRVML_PANEL_PORT    0x38
38 #define CRVML_LVDS_ON       0x00000001
39 #define CRVML_PANEL_ON      0x00000002
40 #define CRVML_BACKLIGHT_OFF 0x00000004
41 
42 /* The PLL Clock register sits on Host bridge */
43 #define CRVML_DEVICE_MCH   0x5001
44 #define CRVML_REG_MCHBAR   0x44
45 #define CRVML_REG_MCHEN    0x54
46 #define CRVML_MCHEN_BIT    (1 << 28)
47 #define CRVML_MCHMAP_SIZE  4096
48 #define CRVML_REG_CLOCK    0xc3c
49 #define CRVML_CLOCK_SHIFT  8
50 #define CRVML_CLOCK_MASK   0x00000f00
51 
52 static struct pci_dev *lpc_dev;
53 static u32 gpio_bar;
54 
55 struct cr_panel {
56 	struct backlight_device *cr_backlight_device;
57 	struct lcd_device *cr_lcd_device;
58 };
59 
60 static int cr_backlight_set_intensity(struct backlight_device *bd)
61 {
62 	int intensity = bd->props.brightness;
63 	u32 addr = gpio_bar + CRVML_PANEL_PORT;
64 	u32 cur = inl(addr);
65 
66 	if (bd->props.power == FB_BLANK_UNBLANK)
67 		intensity = FB_BLANK_UNBLANK;
68 	if (bd->props.fb_blank == FB_BLANK_UNBLANK)
69 		intensity = FB_BLANK_UNBLANK;
70 	if (bd->props.power == FB_BLANK_POWERDOWN)
71 		intensity = FB_BLANK_POWERDOWN;
72 	if (bd->props.fb_blank == FB_BLANK_POWERDOWN)
73 		intensity = FB_BLANK_POWERDOWN;
74 
75 	if (intensity == FB_BLANK_UNBLANK) { /* FULL ON */
76 		cur &= ~CRVML_BACKLIGHT_OFF;
77 		outl(cur, addr);
78 	} else if (intensity == FB_BLANK_POWERDOWN) { /* OFF */
79 		cur |= CRVML_BACKLIGHT_OFF;
80 		outl(cur, addr);
81 	} /* anything else, don't bother */
82 
83 	return 0;
84 }
85 
86 static int cr_backlight_get_intensity(struct backlight_device *bd)
87 {
88 	u32 addr = gpio_bar + CRVML_PANEL_PORT;
89 	u32 cur = inl(addr);
90 	u8 intensity;
91 
92 	if (cur & CRVML_BACKLIGHT_OFF)
93 		intensity = FB_BLANK_POWERDOWN;
94 	else
95 		intensity = FB_BLANK_UNBLANK;
96 
97 	return intensity;
98 }
99 
100 static const struct backlight_ops cr_backlight_ops = {
101 	.get_brightness = cr_backlight_get_intensity,
102 	.update_status = cr_backlight_set_intensity,
103 };
104 
105 static void cr_panel_on(void)
106 {
107 	u32 addr = gpio_bar + CRVML_PANEL_PORT;
108 	u32 cur = inl(addr);
109 
110 	if (!(cur & CRVML_PANEL_ON)) {
111 		/* Make sure LVDS controller is down. */
112 		if (cur & 0x00000001) {
113 			cur &= ~CRVML_LVDS_ON;
114 			outl(cur, addr);
115 		}
116 		/* Power up Panel */
117 		schedule_timeout(HZ / 10);
118 		cur |= CRVML_PANEL_ON;
119 		outl(cur, addr);
120 	}
121 
122 	/* Power up LVDS controller */
123 
124 	if (!(cur & CRVML_LVDS_ON)) {
125 		schedule_timeout(HZ / 10);
126 		outl(cur | CRVML_LVDS_ON, addr);
127 	}
128 }
129 
130 static void cr_panel_off(void)
131 {
132 	u32 addr = gpio_bar + CRVML_PANEL_PORT;
133 	u32 cur = inl(addr);
134 
135 	/* Power down LVDS controller first to avoid high currents */
136 	if (cur & CRVML_LVDS_ON) {
137 		cur &= ~CRVML_LVDS_ON;
138 		outl(cur, addr);
139 	}
140 	if (cur & CRVML_PANEL_ON) {
141 		schedule_timeout(HZ / 10);
142 		outl(cur & ~CRVML_PANEL_ON, addr);
143 	}
144 }
145 
146 static int cr_lcd_set_power(struct lcd_device *ld, int power)
147 {
148 	if (power == FB_BLANK_UNBLANK)
149 		cr_panel_on();
150 	if (power == FB_BLANK_POWERDOWN)
151 		cr_panel_off();
152 
153 	return 0;
154 }
155 
156 static struct lcd_ops cr_lcd_ops = {
157 	.set_power = cr_lcd_set_power,
158 };
159 
160 static int cr_backlight_probe(struct platform_device *pdev)
161 {
162 	struct backlight_properties props;
163 	struct backlight_device *bdp;
164 	struct lcd_device *ldp;
165 	struct cr_panel *crp;
166 	u8 dev_en;
167 
168 	lpc_dev = pci_get_device(PCI_VENDOR_ID_INTEL,
169 					CRVML_DEVICE_LPC, NULL);
170 	if (!lpc_dev) {
171 		pr_err("INTEL CARILLO RANCH LPC not found.\n");
172 		return -ENODEV;
173 	}
174 
175 	pci_read_config_byte(lpc_dev, CRVML_REG_GPIOEN, &dev_en);
176 	if (!(dev_en & CRVML_GPIOEN_BIT)) {
177 		pr_err("Carillo Ranch GPIO device was not enabled.\n");
178 		pci_dev_put(lpc_dev);
179 		return -ENODEV;
180 	}
181 
182 	memset(&props, 0, sizeof(struct backlight_properties));
183 	props.type = BACKLIGHT_RAW;
184 	bdp = devm_backlight_device_register(&pdev->dev, "cr-backlight",
185 					&pdev->dev, NULL, &cr_backlight_ops,
186 					&props);
187 	if (IS_ERR(bdp)) {
188 		pci_dev_put(lpc_dev);
189 		return PTR_ERR(bdp);
190 	}
191 
192 	ldp = devm_lcd_device_register(&pdev->dev, "cr-lcd", &pdev->dev, NULL,
193 					&cr_lcd_ops);
194 	if (IS_ERR(ldp)) {
195 		pci_dev_put(lpc_dev);
196 		return PTR_ERR(ldp);
197 	}
198 
199 	pci_read_config_dword(lpc_dev, CRVML_REG_GPIOBAR,
200 			      &gpio_bar);
201 	gpio_bar &= ~0x3F;
202 
203 	crp = devm_kzalloc(&pdev->dev, sizeof(*crp), GFP_KERNEL);
204 	if (!crp) {
205 		pci_dev_put(lpc_dev);
206 		return -ENOMEM;
207 	}
208 
209 	crp->cr_backlight_device = bdp;
210 	crp->cr_lcd_device = ldp;
211 	crp->cr_backlight_device->props.power = FB_BLANK_UNBLANK;
212 	crp->cr_backlight_device->props.brightness = 0;
213 	cr_backlight_set_intensity(crp->cr_backlight_device);
214 	cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_UNBLANK);
215 
216 	platform_set_drvdata(pdev, crp);
217 
218 	return 0;
219 }
220 
221 static int cr_backlight_remove(struct platform_device *pdev)
222 {
223 	struct cr_panel *crp = platform_get_drvdata(pdev);
224 
225 	crp->cr_backlight_device->props.power = FB_BLANK_POWERDOWN;
226 	crp->cr_backlight_device->props.brightness = 0;
227 	crp->cr_backlight_device->props.max_brightness = 0;
228 	cr_backlight_set_intensity(crp->cr_backlight_device);
229 	cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_POWERDOWN);
230 	pci_dev_put(lpc_dev);
231 
232 	return 0;
233 }
234 
235 static struct platform_driver cr_backlight_driver = {
236 	.probe = cr_backlight_probe,
237 	.remove = cr_backlight_remove,
238 	.driver = {
239 		   .name = "cr_backlight",
240 		   },
241 };
242 
243 static struct platform_device *crp;
244 
245 static int __init cr_backlight_init(void)
246 {
247 	int ret = platform_driver_register(&cr_backlight_driver);
248 
249 	if (ret)
250 		return ret;
251 
252 	crp = platform_device_register_simple("cr_backlight", -1, NULL, 0);
253 	if (IS_ERR(crp)) {
254 		platform_driver_unregister(&cr_backlight_driver);
255 		return PTR_ERR(crp);
256 	}
257 
258 	pr_info("Carillo Ranch Backlight Driver Initialized.\n");
259 
260 	return 0;
261 }
262 
263 static void __exit cr_backlight_exit(void)
264 {
265 	platform_device_unregister(crp);
266 	platform_driver_unregister(&cr_backlight_driver);
267 }
268 
269 module_init(cr_backlight_init);
270 module_exit(cr_backlight_exit);
271 
272 MODULE_AUTHOR("Tungsten Graphics Inc.");
273 MODULE_DESCRIPTION("Carillo Ranch Backlight Driver");
274 MODULE_LICENSE("GPL");
275