162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LCD Lowlevel Control Abstraction 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2003,2004 Hewlett-Packard Company 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/module.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/device.h> 1462306a36Sopenharmony_ci#include <linux/lcd.h> 1562306a36Sopenharmony_ci#include <linux/notifier.h> 1662306a36Sopenharmony_ci#include <linux/ctype.h> 1762306a36Sopenharmony_ci#include <linux/err.h> 1862306a36Sopenharmony_ci#include <linux/fb.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#if defined(CONFIG_FB) || (defined(CONFIG_FB_MODULE) && \ 2262306a36Sopenharmony_ci defined(CONFIG_LCD_CLASS_DEVICE_MODULE)) 2362306a36Sopenharmony_ci/* This callback gets called when something important happens inside a 2462306a36Sopenharmony_ci * framebuffer driver. We're looking if that important event is blanking, 2562306a36Sopenharmony_ci * and if it is, we're switching lcd power as well ... 2662306a36Sopenharmony_ci */ 2762306a36Sopenharmony_cistatic int fb_notifier_callback(struct notifier_block *self, 2862306a36Sopenharmony_ci unsigned long event, void *data) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci struct lcd_device *ld; 3162306a36Sopenharmony_ci struct fb_event *evdata = data; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci ld = container_of(self, struct lcd_device, fb_notif); 3462306a36Sopenharmony_ci if (!ld->ops) 3562306a36Sopenharmony_ci return 0; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 3862306a36Sopenharmony_ci if (!ld->ops->check_fb || ld->ops->check_fb(ld, evdata->info)) { 3962306a36Sopenharmony_ci if (event == FB_EVENT_BLANK) { 4062306a36Sopenharmony_ci if (ld->ops->set_power) 4162306a36Sopenharmony_ci ld->ops->set_power(ld, *(int *)evdata->data); 4262306a36Sopenharmony_ci } else { 4362306a36Sopenharmony_ci if (ld->ops->set_mode) 4462306a36Sopenharmony_ci ld->ops->set_mode(ld, evdata->data); 4562306a36Sopenharmony_ci } 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 4862306a36Sopenharmony_ci return 0; 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic int lcd_register_fb(struct lcd_device *ld) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci memset(&ld->fb_notif, 0, sizeof(ld->fb_notif)); 5462306a36Sopenharmony_ci ld->fb_notif.notifier_call = fb_notifier_callback; 5562306a36Sopenharmony_ci return fb_register_client(&ld->fb_notif); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void lcd_unregister_fb(struct lcd_device *ld) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci fb_unregister_client(&ld->fb_notif); 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci#else 6362306a36Sopenharmony_cistatic int lcd_register_fb(struct lcd_device *ld) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci return 0; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic inline void lcd_unregister_fb(struct lcd_device *ld) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci#endif /* CONFIG_FB */ 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr, 7462306a36Sopenharmony_ci char *buf) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci int rc; 7762306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 8062306a36Sopenharmony_ci if (ld->ops && ld->ops->get_power) 8162306a36Sopenharmony_ci rc = sprintf(buf, "%d\n", ld->ops->get_power(ld)); 8262306a36Sopenharmony_ci else 8362306a36Sopenharmony_ci rc = -ENXIO; 8462306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return rc; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic ssize_t lcd_power_store(struct device *dev, 9062306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci int rc; 9362306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 9462306a36Sopenharmony_ci unsigned long power; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci rc = kstrtoul(buf, 0, &power); 9762306a36Sopenharmony_ci if (rc) 9862306a36Sopenharmony_ci return rc; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci rc = -ENXIO; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 10362306a36Sopenharmony_ci if (ld->ops && ld->ops->set_power) { 10462306a36Sopenharmony_ci pr_debug("set power to %lu\n", power); 10562306a36Sopenharmony_ci ld->ops->set_power(ld, power); 10662306a36Sopenharmony_ci rc = count; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return rc; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(lcd_power); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic ssize_t contrast_show(struct device *dev, 11562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci int rc = -ENXIO; 11862306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 12162306a36Sopenharmony_ci if (ld->ops && ld->ops->get_contrast) 12262306a36Sopenharmony_ci rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld)); 12362306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return rc; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic ssize_t contrast_store(struct device *dev, 12962306a36Sopenharmony_ci struct device_attribute *attr, const char *buf, size_t count) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci int rc; 13262306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 13362306a36Sopenharmony_ci unsigned long contrast; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci rc = kstrtoul(buf, 0, &contrast); 13662306a36Sopenharmony_ci if (rc) 13762306a36Sopenharmony_ci return rc; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci rc = -ENXIO; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 14262306a36Sopenharmony_ci if (ld->ops && ld->ops->set_contrast) { 14362306a36Sopenharmony_ci pr_debug("set contrast to %lu\n", contrast); 14462306a36Sopenharmony_ci ld->ops->set_contrast(ld, contrast); 14562306a36Sopenharmony_ci rc = count; 14662306a36Sopenharmony_ci } 14762306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci return rc; 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(contrast); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic ssize_t max_contrast_show(struct device *dev, 15462306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return sprintf(buf, "%d\n", ld->props.max_contrast); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(max_contrast); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic struct class *lcd_class; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void lcd_device_release(struct device *dev) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct lcd_device *ld = to_lcd_device(dev); 16762306a36Sopenharmony_ci kfree(ld); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic struct attribute *lcd_device_attrs[] = { 17162306a36Sopenharmony_ci &dev_attr_lcd_power.attr, 17262306a36Sopenharmony_ci &dev_attr_contrast.attr, 17362306a36Sopenharmony_ci &dev_attr_max_contrast.attr, 17462306a36Sopenharmony_ci NULL, 17562306a36Sopenharmony_ci}; 17662306a36Sopenharmony_ciATTRIBUTE_GROUPS(lcd_device); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci/** 17962306a36Sopenharmony_ci * lcd_device_register - register a new object of lcd_device class. 18062306a36Sopenharmony_ci * @name: the name of the new object(must be the same as the name of the 18162306a36Sopenharmony_ci * respective framebuffer device). 18262306a36Sopenharmony_ci * @parent: pointer to the parent's struct device . 18362306a36Sopenharmony_ci * @devdata: an optional pointer to be stored in the device. The 18462306a36Sopenharmony_ci * methods may retrieve it by using lcd_get_data(ld). 18562306a36Sopenharmony_ci * @ops: the lcd operations structure. 18662306a36Sopenharmony_ci * 18762306a36Sopenharmony_ci * Creates and registers a new lcd device. Returns either an ERR_PTR() 18862306a36Sopenharmony_ci * or a pointer to the newly allocated device. 18962306a36Sopenharmony_ci */ 19062306a36Sopenharmony_cistruct lcd_device *lcd_device_register(const char *name, struct device *parent, 19162306a36Sopenharmony_ci void *devdata, struct lcd_ops *ops) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct lcd_device *new_ld; 19462306a36Sopenharmony_ci int rc; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci pr_debug("lcd_device_register: name=%s\n", name); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci new_ld = kzalloc(sizeof(struct lcd_device), GFP_KERNEL); 19962306a36Sopenharmony_ci if (!new_ld) 20062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci mutex_init(&new_ld->ops_lock); 20362306a36Sopenharmony_ci mutex_init(&new_ld->update_lock); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci new_ld->dev.class = lcd_class; 20662306a36Sopenharmony_ci new_ld->dev.parent = parent; 20762306a36Sopenharmony_ci new_ld->dev.release = lcd_device_release; 20862306a36Sopenharmony_ci dev_set_name(&new_ld->dev, "%s", name); 20962306a36Sopenharmony_ci dev_set_drvdata(&new_ld->dev, devdata); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci new_ld->ops = ops; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci rc = device_register(&new_ld->dev); 21462306a36Sopenharmony_ci if (rc) { 21562306a36Sopenharmony_ci put_device(&new_ld->dev); 21662306a36Sopenharmony_ci return ERR_PTR(rc); 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci rc = lcd_register_fb(new_ld); 22062306a36Sopenharmony_ci if (rc) { 22162306a36Sopenharmony_ci device_unregister(&new_ld->dev); 22262306a36Sopenharmony_ci return ERR_PTR(rc); 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci return new_ld; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ciEXPORT_SYMBOL(lcd_device_register); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci/** 23062306a36Sopenharmony_ci * lcd_device_unregister - unregisters a object of lcd_device class. 23162306a36Sopenharmony_ci * @ld: the lcd device object to be unregistered and freed. 23262306a36Sopenharmony_ci * 23362306a36Sopenharmony_ci * Unregisters a previously registered via lcd_device_register object. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_civoid lcd_device_unregister(struct lcd_device *ld) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci if (!ld) 23862306a36Sopenharmony_ci return; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci mutex_lock(&ld->ops_lock); 24162306a36Sopenharmony_ci ld->ops = NULL; 24262306a36Sopenharmony_ci mutex_unlock(&ld->ops_lock); 24362306a36Sopenharmony_ci lcd_unregister_fb(ld); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci device_unregister(&ld->dev); 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ciEXPORT_SYMBOL(lcd_device_unregister); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic void devm_lcd_device_release(struct device *dev, void *res) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci struct lcd_device *lcd = *(struct lcd_device **)res; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci lcd_device_unregister(lcd); 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic int devm_lcd_device_match(struct device *dev, void *res, void *data) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci struct lcd_device **r = res; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci return *r == data; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci/** 26462306a36Sopenharmony_ci * devm_lcd_device_register - resource managed lcd_device_register() 26562306a36Sopenharmony_ci * @dev: the device to register 26662306a36Sopenharmony_ci * @name: the name of the device 26762306a36Sopenharmony_ci * @parent: a pointer to the parent device 26862306a36Sopenharmony_ci * @devdata: an optional pointer to be stored for private driver use 26962306a36Sopenharmony_ci * @ops: the lcd operations structure 27062306a36Sopenharmony_ci * 27162306a36Sopenharmony_ci * @return a struct lcd on success, or an ERR_PTR on error 27262306a36Sopenharmony_ci * 27362306a36Sopenharmony_ci * Managed lcd_device_register(). The lcd_device returned from this function 27462306a36Sopenharmony_ci * are automatically freed on driver detach. See lcd_device_register() 27562306a36Sopenharmony_ci * for more information. 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_cistruct lcd_device *devm_lcd_device_register(struct device *dev, 27862306a36Sopenharmony_ci const char *name, struct device *parent, 27962306a36Sopenharmony_ci void *devdata, struct lcd_ops *ops) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct lcd_device **ptr, *lcd; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci ptr = devres_alloc(devm_lcd_device_release, sizeof(*ptr), GFP_KERNEL); 28462306a36Sopenharmony_ci if (!ptr) 28562306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci lcd = lcd_device_register(name, parent, devdata, ops); 28862306a36Sopenharmony_ci if (!IS_ERR(lcd)) { 28962306a36Sopenharmony_ci *ptr = lcd; 29062306a36Sopenharmony_ci devres_add(dev, ptr); 29162306a36Sopenharmony_ci } else { 29262306a36Sopenharmony_ci devres_free(ptr); 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci return lcd; 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ciEXPORT_SYMBOL(devm_lcd_device_register); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci/** 30062306a36Sopenharmony_ci * devm_lcd_device_unregister - resource managed lcd_device_unregister() 30162306a36Sopenharmony_ci * @dev: the device to unregister 30262306a36Sopenharmony_ci * @ld: the lcd device to unregister 30362306a36Sopenharmony_ci * 30462306a36Sopenharmony_ci * Deallocated a lcd allocated with devm_lcd_device_register(). Normally 30562306a36Sopenharmony_ci * this function will not need to be called and the resource management 30662306a36Sopenharmony_ci * code will ensure that the resource is freed. 30762306a36Sopenharmony_ci */ 30862306a36Sopenharmony_civoid devm_lcd_device_unregister(struct device *dev, struct lcd_device *ld) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci int rc; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci rc = devres_release(dev, devm_lcd_device_release, 31362306a36Sopenharmony_ci devm_lcd_device_match, ld); 31462306a36Sopenharmony_ci WARN_ON(rc); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ciEXPORT_SYMBOL(devm_lcd_device_unregister); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic void __exit lcd_class_exit(void) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci class_destroy(lcd_class); 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic int __init lcd_class_init(void) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci lcd_class = class_create("lcd"); 32762306a36Sopenharmony_ci if (IS_ERR(lcd_class)) { 32862306a36Sopenharmony_ci pr_warn("Unable to create backlight class; errno = %ld\n", 32962306a36Sopenharmony_ci PTR_ERR(lcd_class)); 33062306a36Sopenharmony_ci return PTR_ERR(lcd_class); 33162306a36Sopenharmony_ci } 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci lcd_class->dev_groups = lcd_device_groups; 33462306a36Sopenharmony_ci return 0; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci/* 33862306a36Sopenharmony_ci * if this is compiled into the kernel, we need to ensure that the 33962306a36Sopenharmony_ci * class is registered before users of the class try to register lcd's 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_cipostcore_initcall(lcd_class_init); 34262306a36Sopenharmony_cimodule_exit(lcd_class_exit); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 34562306a36Sopenharmony_ciMODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>, Andrew Zabolotny <zap@homelink.ru>"); 34662306a36Sopenharmony_ciMODULE_DESCRIPTION("LCD Lowlevel Control Abstraction"); 347