diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b444606..4e86e04 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -88,6 +88,13 @@ config SUNXI_PWM a sysfs interface at /sys/class/pwm-sunxi as well as the kernel pwm interface. +config OLINUXINO_LCD + tristate "PWM driver for LCD-OLinuXino-15.6FHD + depends on ARCH_SUN7I + help + This driver implements software pwm support for + OLinuXino-15.6FHD. The lcd needs two pwms: backlight and + contrast. config AB8500_PWM bool "AB8500 PWM support" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 57a707c..bffa6c2 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,3 +55,4 @@ obj-$(CONFIG_SENSORS_AK8975) += akm8975.o obj-$(CONFIG_SUN4I_VIBRATOR) += sun4i-vibrator.o obj-$(CONFIG_SUNXI_DBGREG) += sunxi-dbgreg.o obj-$(CONFIG_SUNXI_PWM) += pwm-sunxi.o +obj-$(CONFIG_OLINUXINO_LCD) += olinuxino-lcd.o diff --git a/drivers/misc/olinuxino-lcd.c b/drivers/misc/olinuxino-lcd.c new file mode 100644 index 0000000..c854a52 --- /dev/null +++ b/drivers/misc/olinuxino-lcd.c @@ -0,0 +1,388 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +struct olinuxino_lcd_platform_data { + unsigned gpio_handler; + + script_gpio_set_t info; + + char pin_name[16]; + char device_name[64]; + + unsigned duty; + unsigned last_state; + + ktime_t period; + ktime_t pulse_on; + ktime_t pulse_off; + struct hrtimer hr_timer; + + struct device *dev; +}; + +static DEFINE_MUTEX(sysfs_lock); + +static struct olinuxino_lcd_platform_data *p_backlight; +static struct olinuxino_lcd_platform_data *p_contrast; + +static int sunxi_gpio_is_valid(struct olinuxino_lcd_platform_data *gpio) +{ + if(gpio->gpio_handler) + return 1; + + return 0; +} + +static int sunxi_direction_input(struct olinuxino_lcd_platform_data *gpio) +{ + int ret; + + if(sunxi_gpio_is_valid(gpio)){ + ret = gpio_set_one_pin_io_status(gpio->gpio_handler, 0, gpio->pin_name); + }else{ + printk(KERN_ERR "%s: requested gpio has no valid handler\n", __func__); + return 1; + } + return ret; +} + +static void sunxi_gpio_set_value(struct olinuxino_lcd_platform_data *gpio, int value) +{ + if(sunxi_gpio_is_valid(gpio)){ + gpio_write_one_pin_value(gpio->gpio_handler, value, gpio->pin_name); + }else{ + printk(KERN_ERR "%s: requested gpio has no valid handler\n", __func__); + } +} + +static int sunxi_direction_output(struct olinuxino_lcd_platform_data *gpio, int value) +{ + int ret; + + if(!sunxi_gpio_is_valid(gpio)){ + printk(KERN_ERR "%s: requested gpio has no valid handler\n", __func__); + return 1; + } + + ret = gpio_set_one_pin_io_status(gpio->gpio_handler, 1, gpio->pin_name); + + if (!ret) + ret = gpio_write_one_pin_value(gpio->gpio_handler, + value, gpio->pin_name); + + return ret; +} + +static ssize_t duty_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct olinuxino_lcd_platform_data *device; + ssize_t ret; + + device = dev_get_drvdata(dev); + + mutex_lock(&sysfs_lock); + + ret = sprintf(buf, "%d\n", device->duty); + + mutex_unlock(&sysfs_lock); + return ret; +} + +static ssize_t duty_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret; + long duty; + u64 v; + struct olinuxino_lcd_platform_data *device; + + device = dev_get_drvdata(dev); + + mutex_lock(&sysfs_lock); + + ret = strict_strtol(buf, 0, &duty); + if(ret) + return -EINVAL; + + if(duty < 0 || duty > 100) + return -EINVAL; + + device->duty = duty; + v = device->period.tv64; + do_div(v, 100); + + device->pulse_on.tv64 = v*duty; + device->pulse_off.tv64 = v*(100-duty); + + mutex_unlock(&sysfs_lock); + + return count; +} + +static DEVICE_ATTR(duty, 0666, duty_show, duty_store); + +enum hrtimer_restart olinuxino_lcd_hrtimer_callback(struct hrtimer *timer) +{ + unsigned char current_state; + struct olinuxino_lcd_platform_data *dev; + ktime_t now = ktime_get(); + + dev = container_of(timer, struct olinuxino_lcd_platform_data, hr_timer); + + if(!dev->duty){ + sunxi_gpio_set_value(dev, 0); + hrtimer_forward(timer, now, dev->pulse_off); + }else if(dev->duty == 100){ + sunxi_gpio_set_value(dev, 1); + hrtimer_forward(timer, now, dev->pulse_on); + }else{ + current_state = dev->last_state; + if(current_state){ + sunxi_gpio_set_value(dev, 0); + dev->last_state = 0; + hrtimer_forward(timer, now, dev->pulse_off); + }else{ + sunxi_gpio_set_value(dev, 1); + dev->last_state = 1; + hrtimer_forward(timer, now, dev->pulse_on); + } + } + + return HRTIMER_RESTART; +} + +static struct attribute *olinuxino_lcd_sysfs_entries[] = { + &dev_attr_duty.attr, + NULL, +}; + +static struct attribute_group olinuxino_lcd_attribute_group = { + .attrs = (struct attribute **)olinuxino_lcd_sysfs_entries, +}; + +static struct class olinuxino_lcd_class = { + .name = "olinuxino_lcd", + .owner = THIS_MODULE, +}; + +static ssize_t __init olinuxino_lcd_init(void) +{ + int err; + int olinuxino_lcd_used = 0; + + printk(KERN_INFO "%s()\n", __func__); + + /* Check if olinuxino_lcd is used */ + err = script_parser_fetch("olinuxino_lcd_para", "olinuxino_lcd_used", &olinuxino_lcd_used, sizeof(olinuxino_lcd_used)/sizeof(int)); + if (!olinuxino_lcd_used || err) { + printk(KERN_INFO "%s: olinuxino_lcd is not used in config\n", __func__); + return -EINVAL; + } + + p_backlight = kzalloc(sizeof(struct olinuxino_lcd_platform_data), GFP_KERNEL); + p_contrast = kzalloc(sizeof(struct olinuxino_lcd_platform_data), GFP_KERNEL); + + if (!p_backlight || !p_contrast) { + printk(KERN_INFO "%s: failed to kzalloc memory\n", __func__); + err = -ENOMEM; + goto exit0; + } + + sprintf(p_backlight->pin_name, "backlight_pin"); + sprintf(p_contrast->pin_name, "contrast_pin"); + + sprintf(p_backlight->device_name, "backlight"); + sprintf(p_contrast->device_name, "contrast"); + + /* Read GPIO data */ + err = script_parser_fetch("olinuxino_lcd_para", p_backlight->pin_name, + (int *)&p_backlight->info, + sizeof(script_gpio_set_t)); + + if (err) { + printk(KERN_INFO "%s failed to find %s\n", __func__, + p_backlight->pin_name); + goto exit0; + } + err = script_parser_fetch("olinuxino_lcd_para", p_contrast->pin_name, + (int *)&p_contrast->info, + sizeof(script_gpio_set_t)); + if (err) { + printk(KERN_INFO "%s failed to find %s\n", __func__, + p_contrast->pin_name); + goto exit0; + } + + /* Request gpio */ + p_backlight->gpio_handler = gpio_request_ex("olinuxino_lcd_para", + p_backlight->pin_name); + if (!p_backlight->gpio_handler) { + printk(KERN_INFO "%s: cannot request %s, already used ?\n", + __func__, p_backlight->pin_name); + goto exit0; + } + p_contrast->gpio_handler = gpio_request_ex("olinuxino_lcd_para", + p_contrast->pin_name); + if (!p_contrast->gpio_handler) { + printk(KERN_INFO "%s: cannot request %s, already used ?\n", + __func__, p_contrast->pin_name); + goto exit0; + } + + printk(KERN_INFO "%s: backlight registered @ port:%d, num:%d\n", + __func__, p_backlight->info.port, p_backlight->info.port_num); + printk(KERN_INFO "%s: contrast registered @ port:%d, num:%d\n", + __func__, p_contrast->info.port, p_contrast->info.port_num); + + err = class_register(&olinuxino_lcd_class); + if(err < 0){ + printk(KERN_INFO "%s: unable to register class\n", __func__); + goto exit0; + } + + + /* Make sysfs devices */ + p_backlight->dev = device_create(&olinuxino_lcd_class, NULL, + MKDEV(0, 0), p_backlight, + p_backlight->device_name); + if(IS_ERR(p_backlight->dev)) { + printk(KERN_INFO "%s: device_create failed\n", __func__); + err = PTR_ERR(p_backlight->dev); + goto exit1; + } + p_contrast->dev = device_create(&olinuxino_lcd_class, NULL, + MKDEV(0, 0), p_contrast, + p_contrast->device_name); + if(IS_ERR(p_backlight->dev)) { + printk(KERN_INFO "%s: device_create failed\n", __func__); + err = PTR_ERR(p_contrast->dev); + goto exit1; + } + + err = sysfs_create_group(&p_backlight->dev->kobj, + &olinuxino_lcd_attribute_group); + if(err < 0){ + printk(KERN_INFO "%s: failed to create sysfs device attributes\n", __func__); + goto exit2; + } + err = sysfs_create_group(&p_contrast->dev->kobj, + &olinuxino_lcd_attribute_group); + if(err < 0){ + printk(KERN_INFO "%s: failed to create sysfs device attributes\n", __func__); + goto exit2; + } + + p_backlight->duty = 100; + p_contrast->duty = 100; + + /* Set period to 1kHz, 50% duty */ + p_backlight->period = ktime_set(0, 1000000); + p_contrast->period = ktime_set(0, 1000000); + + p_backlight->pulse_on = ktime_set(0, 1000000); + p_contrast->pulse_on = ktime_set(0, 1000000); + + p_backlight->pulse_off = ktime_set(0, 0); + p_contrast->pulse_off = ktime_set(0, 0); + + /* Init gpio as output */ + sunxi_direction_output(p_backlight, 0); + p_backlight->last_state = 0; + + sunxi_direction_output(p_contrast, 0); + p_contrast->last_state = 0; + + hrtimer_init(&p_backlight->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_init(&p_contrast->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + p_backlight->hr_timer.function = &olinuxino_lcd_hrtimer_callback; + p_contrast->hr_timer.function = &olinuxino_lcd_hrtimer_callback; + + hrtimer_start(&p_backlight->hr_timer, p_backlight->pulse_off, HRTIMER_MODE_REL); + hrtimer_start(&p_contrast->hr_timer, p_contrast->pulse_off, HRTIMER_MODE_REL); + + return 0; + +exit2: + sysfs_remove_group(&p_backlight->dev->kobj, &olinuxino_lcd_attribute_group); + sysfs_remove_group(&p_contrast->dev->kobj, &olinuxino_lcd_attribute_group); + +exit1: + /* Unregister devices */ + if(p_backlight->dev) + device_unregister(p_backlight->dev); + if(p_contrast->dev) + device_unregister(p_contrast->dev); + + /* Unregister class */ + class_unregister(&olinuxino_lcd_class); + +exit0: + if (err != -ENOMEM) { + + if(p_backlight->gpio_handler){ + gpio_release(p_backlight->gpio_handler, 1); + } + + if(p_contrast->gpio_handler){ + gpio_release(p_contrast->gpio_handler, 1); + } + } + kfree(p_contrast); + kfree(p_backlight); + return err; +} + +static void __exit olinuxino_lcd_exit(void) +{ + + printk(KERN_INFO "%s()\n", __func__); + + hrtimer_cancel(&p_backlight->hr_timer); + hrtimer_cancel(&p_contrast->hr_timer); + + /* Make gpios as inputs */ + sunxi_direction_input(p_backlight); + sunxi_direction_input(p_contrast); + + /* Release gpios */ + if(p_backlight->gpio_handler){ + gpio_release(p_backlight->gpio_handler, 1); + printk(KERN_INFO "%s: gpio %s released\n", __func__, p_backlight->info.gpio_name); + } + if(p_contrast->gpio_handler){ + gpio_release(p_contrast->gpio_handler, 1); + printk(KERN_INFO "%s: gpio %s released\n", __func__, p_contrast->info.gpio_name); + } + + sysfs_remove_group(&p_backlight->dev->kobj, &olinuxino_lcd_attribute_group); + sysfs_remove_group(&p_contrast->dev->kobj, &olinuxino_lcd_attribute_group); + + if(p_backlight->dev) + device_unregister(p_backlight->dev); + if(p_contrast->dev) + device_unregister(p_contrast->dev); + + class_unregister(&olinuxino_lcd_class); +} + +module_init(olinuxino_lcd_init); +module_exit(olinuxino_lcd_exit); + +MODULE_AUTHOR("Stefan Mavrodiev