lcd.c 8.68 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7
/*
 * LCD Lowlevel Control Abstraction
 *
 * Copyright (C) 2003,2004 Hewlett-Packard Company
 *
 */

8 9
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

Linus Torvalds's avatar
Linus Torvalds committed
10 11 12 13 14 15 16 17
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/lcd.h>
#include <linux/notifier.h>
#include <linux/ctype.h>
#include <linux/err.h>
#include <linux/fb.h>
18
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
19

20 21 22 23 24 25 26 27 28 29 30 31 32
#if defined(CONFIG_FB) || (defined(CONFIG_FB_MODULE) && \
			   defined(CONFIG_LCD_CLASS_DEVICE_MODULE))
/* This callback gets called when something important happens inside a
 * framebuffer driver. We're looking if that important event is blanking,
 * and if it is, we're switching lcd power as well ...
 */
static int fb_notifier_callback(struct notifier_block *self,
				 unsigned long event, void *data)
{
	struct lcd_device *ld;
	struct fb_event *evdata = data;

	/* If we aren't interested in this event, skip it immediately ... */
33 34 35 36
	switch (event) {
	case FB_EVENT_BLANK:
	case FB_EVENT_MODE_CHANGE:
	case FB_EVENT_MODE_CHANGE_ALL:
37 38
	case FB_EARLY_EVENT_BLANK:
	case FB_R_EARLY_EVENT_BLANK:
39 40
		break;
	default:
41
		return 0;
42
	}
43 44

	ld = container_of(self, struct lcd_device, fb_notif);
45 46 47
	if (!ld->ops)
		return 0;

48
	mutex_lock(&ld->ops_lock);
49
	if (!ld->ops->check_fb || ld->ops->check_fb(ld, evdata->info)) {
50 51 52
		if (event == FB_EVENT_BLANK) {
			if (ld->ops->set_power)
				ld->ops->set_power(ld, *(int *)evdata->data);
53 54 55 56 57 58 59 60
		} else if (event == FB_EARLY_EVENT_BLANK) {
			if (ld->ops->early_set_power)
				ld->ops->early_set_power(ld,
						*(int *)evdata->data);
		} else if (event == FB_R_EARLY_EVENT_BLANK) {
			if (ld->ops->r_early_set_power)
				ld->ops->r_early_set_power(ld,
						*(int *)evdata->data);
61 62 63 64
		} else {
			if (ld->ops->set_mode)
				ld->ops->set_mode(ld, evdata->data);
		}
65
	}
66
	mutex_unlock(&ld->ops_lock);
67 68 69 70 71
	return 0;
}

static int lcd_register_fb(struct lcd_device *ld)
{
72
	memset(&ld->fb_notif, 0, sizeof(ld->fb_notif));
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
	ld->fb_notif.notifier_call = fb_notifier_callback;
	return fb_register_client(&ld->fb_notif);
}

static void lcd_unregister_fb(struct lcd_device *ld)
{
	fb_unregister_client(&ld->fb_notif);
}
#else
static int lcd_register_fb(struct lcd_device *ld)
{
	return 0;
}

static inline void lcd_unregister_fb(struct lcd_device *ld)
{
}
#endif /* CONFIG_FB */

92
static ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr,
93
		char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
94 95
{
	int rc;
96
	struct lcd_device *ld = to_lcd_device(dev);
Linus Torvalds's avatar
Linus Torvalds committed
97

98 99 100
	mutex_lock(&ld->ops_lock);
	if (ld->ops && ld->ops->get_power)
		rc = sprintf(buf, "%d\n", ld->ops->get_power(ld));
Linus Torvalds's avatar
Linus Torvalds committed
101 102
	else
		rc = -ENXIO;
103
	mutex_unlock(&ld->ops_lock);
Linus Torvalds's avatar
Linus Torvalds committed
104 105 106 107

	return rc;
}

108
static ssize_t lcd_power_store(struct device *dev,
109
		struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
110
{
111
	int rc;
112
	struct lcd_device *ld = to_lcd_device(dev);
Jingoo Han's avatar
Jingoo Han committed
113
	unsigned long power;
Linus Torvalds's avatar
Linus Torvalds committed
114

Jingoo Han's avatar
Jingoo Han committed
115 116 117
	rc = kstrtoul(buf, 0, &power);
	if (rc)
		return rc;
Linus Torvalds's avatar
Linus Torvalds committed
118

119 120
	rc = -ENXIO;

121 122
	mutex_lock(&ld->ops_lock);
	if (ld->ops && ld->ops->set_power) {
123
		pr_debug("set power to %lu\n", power);
124
		ld->ops->set_power(ld, power);
Linus Torvalds's avatar
Linus Torvalds committed
125
		rc = count;
126
	}
127
	mutex_unlock(&ld->ops_lock);
Linus Torvalds's avatar
Linus Torvalds committed
128 129 130

	return rc;
}
131
static DEVICE_ATTR_RW(lcd_power);
Linus Torvalds's avatar
Linus Torvalds committed
132

133
static ssize_t contrast_show(struct device *dev,
134
		struct device_attribute *attr, char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
135
{
136
	int rc = -ENXIO;
137
	struct lcd_device *ld = to_lcd_device(dev);
Linus Torvalds's avatar
Linus Torvalds committed
138

139 140 141 142
	mutex_lock(&ld->ops_lock);
	if (ld->ops && ld->ops->get_contrast)
		rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld));
	mutex_unlock(&ld->ops_lock);
Linus Torvalds's avatar
Linus Torvalds committed
143 144 145 146

	return rc;
}

147
static ssize_t contrast_store(struct device *dev,
148
		struct device_attribute *attr, const char *buf, size_t count)
Linus Torvalds's avatar
Linus Torvalds committed
149
{
150
	int rc;
151
	struct lcd_device *ld = to_lcd_device(dev);
Jingoo Han's avatar
Jingoo Han committed
152
	unsigned long contrast;
Linus Torvalds's avatar
Linus Torvalds committed
153

Jingoo Han's avatar
Jingoo Han committed
154 155 156
	rc = kstrtoul(buf, 0, &contrast);
	if (rc)
		return rc;
Linus Torvalds's avatar
Linus Torvalds committed
157

158 159
	rc = -ENXIO;

160 161
	mutex_lock(&ld->ops_lock);
	if (ld->ops && ld->ops->set_contrast) {
162
		pr_debug("set contrast to %lu\n", contrast);
163
		ld->ops->set_contrast(ld, contrast);
Linus Torvalds's avatar
Linus Torvalds committed
164
		rc = count;
165
	}
166
	mutex_unlock(&ld->ops_lock);
Linus Torvalds's avatar
Linus Torvalds committed
167 168 169

	return rc;
}
170
static DEVICE_ATTR_RW(contrast);
Linus Torvalds's avatar
Linus Torvalds committed
171

172
static ssize_t max_contrast_show(struct device *dev,
173
		struct device_attribute *attr, char *buf)
Linus Torvalds's avatar
Linus Torvalds committed
174
{
175
	struct lcd_device *ld = to_lcd_device(dev);
Linus Torvalds's avatar
Linus Torvalds committed
176

177
	return sprintf(buf, "%d\n", ld->props.max_contrast);
Linus Torvalds's avatar
Linus Torvalds committed
178
}
179
static DEVICE_ATTR_RO(max_contrast);
Linus Torvalds's avatar
Linus Torvalds committed
180

181
static struct class *lcd_class;
182 183

static void lcd_device_release(struct device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
184 185 186 187 188
{
	struct lcd_device *ld = to_lcd_device(dev);
	kfree(ld);
}

189 190 191 192 193
static struct attribute *lcd_device_attrs[] = {
	&dev_attr_lcd_power.attr,
	&dev_attr_contrast.attr,
	&dev_attr_max_contrast.attr,
	NULL,
Linus Torvalds's avatar
Linus Torvalds committed
194
};
195
ATTRIBUTE_GROUPS(lcd_device);
Linus Torvalds's avatar
Linus Torvalds committed
196 197 198 199 200

/**
 * lcd_device_register - register a new object of lcd_device class.
 * @name: the name of the new object(must be the same as the name of the
 *   respective framebuffer device).
201 202
 * @devdata: an optional pointer to be stored in the device. The
 *   methods may retrieve it by using lcd_get_data(ld).
203
 * @ops: the lcd operations structure.
Linus Torvalds's avatar
Linus Torvalds committed
204
 *
205
 * Creates and registers a new lcd device. Returns either an ERR_PTR()
Linus Torvalds's avatar
Linus Torvalds committed
206 207
 * or a pointer to the newly allocated device.
 */
208 209
struct lcd_device *lcd_device_register(const char *name, struct device *parent,
		void *devdata, struct lcd_ops *ops)
Linus Torvalds's avatar
Linus Torvalds committed
210 211
{
	struct lcd_device *new_ld;
212
	int rc;
Linus Torvalds's avatar
Linus Torvalds committed
213 214 215

	pr_debug("lcd_device_register: name=%s\n", name);

216
	new_ld = kzalloc(sizeof(struct lcd_device), GFP_KERNEL);
217
	if (!new_ld)
218
		return ERR_PTR(-ENOMEM);
Linus Torvalds's avatar
Linus Torvalds committed
219

220
	mutex_init(&new_ld->ops_lock);
221
	mutex_init(&new_ld->update_lock);
Linus Torvalds's avatar
Linus Torvalds committed
222

223 224 225
	new_ld->dev.class = lcd_class;
	new_ld->dev.parent = parent;
	new_ld->dev.release = lcd_device_release;
226
	dev_set_name(&new_ld->dev, "%s", name);
227 228 229
	dev_set_drvdata(&new_ld->dev, devdata);

	rc = device_register(&new_ld->dev);
230
	if (rc) {
231
		kfree(new_ld);
Linus Torvalds's avatar
Linus Torvalds committed
232 233 234
		return ERR_PTR(rc);
	}

235
	rc = lcd_register_fb(new_ld);
236
	if (rc) {
237
		device_unregister(&new_ld->dev);
238 239
		return ERR_PTR(rc);
	}
Linus Torvalds's avatar
Linus Torvalds committed
240

241
	new_ld->ops = ops;
Linus Torvalds's avatar
Linus Torvalds committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

	return new_ld;
}
EXPORT_SYMBOL(lcd_device_register);

/**
 * lcd_device_unregister - unregisters a object of lcd_device class.
 * @ld: the lcd device object to be unregistered and freed.
 *
 * Unregisters a previously registered via lcd_device_register object.
 */
void lcd_device_unregister(struct lcd_device *ld)
{
	if (!ld)
		return;

258 259 260
	mutex_lock(&ld->ops_lock);
	ld->ops = NULL;
	mutex_unlock(&ld->ops_lock);
261
	lcd_unregister_fb(ld);
262 263

	device_unregister(&ld->dev);
Linus Torvalds's avatar
Linus Torvalds committed
264 265 266
}
EXPORT_SYMBOL(lcd_device_unregister);

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
static void devm_lcd_device_release(struct device *dev, void *res)
{
	struct lcd_device *lcd = *(struct lcd_device **)res;

	lcd_device_unregister(lcd);
}

static int devm_lcd_device_match(struct device *dev, void *res, void *data)
{
	struct lcd_device **r = res;

	return *r == data;
}

/**
 * devm_lcd_device_register - resource managed lcd_device_register()
 * @dev: the device to register
 * @name: the name of the device
 * @parent: a pointer to the parent device
 * @devdata: an optional pointer to be stored for private driver use
 * @ops: the lcd operations structure
 *
 * @return a struct lcd on success, or an ERR_PTR on error
 *
 * Managed lcd_device_register(). The lcd_device returned from this function
 * are automatically freed on driver detach. See lcd_device_register()
 * for more information.
 */
struct lcd_device *devm_lcd_device_register(struct device *dev,
		const char *name, struct device *parent,
		void *devdata, struct lcd_ops *ops)
{
	struct lcd_device **ptr, *lcd;

	ptr = devres_alloc(devm_lcd_device_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	lcd = lcd_device_register(name, parent, devdata, ops);
	if (!IS_ERR(lcd)) {
		*ptr = lcd;
		devres_add(dev, ptr);
	} else {
		devres_free(ptr);
	}

	return lcd;
}
EXPORT_SYMBOL(devm_lcd_device_register);

/**
 * devm_lcd_device_unregister - resource managed lcd_device_unregister()
 * @dev: the device to unregister
 * @ld: the lcd device to unregister
 *
 * Deallocated a lcd allocated with devm_lcd_device_register(). Normally
 * this function will not need to be called and the resource management
 * code will ensure that the resource is freed.
 */
void devm_lcd_device_unregister(struct device *dev, struct lcd_device *ld)
{
	int rc;

	rc = devres_release(dev, devm_lcd_device_release,
				devm_lcd_device_match, ld);
	WARN_ON(rc);
}
EXPORT_SYMBOL(devm_lcd_device_unregister);


Linus Torvalds's avatar
Linus Torvalds committed
337 338
static void __exit lcd_class_exit(void)
{
339
	class_destroy(lcd_class);
Linus Torvalds's avatar
Linus Torvalds committed
340 341 342 343
}

static int __init lcd_class_init(void)
{
344 345
	lcd_class = class_create(THIS_MODULE, "lcd");
	if (IS_ERR(lcd_class)) {
346 347
		pr_warn("Unable to create backlight class; errno = %ld\n",
			PTR_ERR(lcd_class));
348 349 350
		return PTR_ERR(lcd_class);
	}

351
	lcd_class->dev_groups = lcd_device_groups;
352
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
353 354 355 356 357 358 359 360 361 362 363 364
}

/*
 * if this is compiled into the kernel, we need to ensure that the
 * class is registered before users of the class try to register lcd's
 */
postcore_initcall(lcd_class_init);
module_exit(lcd_class_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>, Andrew Zabolotny <zap@homelink.ru>");
MODULE_DESCRIPTION("LCD Lowlevel Control Abstraction");