1/* 2 * Copyright (C) 2008 Atmel Corporation 3 * 4 * Backlight driver using Atmel PWM peripheral. 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 as published by 8 * the Free Software Foundation. 9 */ 10#include <linux/init.h> 11#include <linux/kernel.h> 12#include <linux/module.h> 13#include <linux/platform_device.h> 14#include <linux/fb.h> 15#include <linux/clk.h> 16#include <linux/gpio.h> 17#include <linux/backlight.h> 18#include <linux/atmel_pwm.h> 19#include <linux/atmel-pwm-bl.h> 20#include <linux/slab.h> 21 22struct atmel_pwm_bl { 23 const struct atmel_pwm_bl_platform_data *pdata; 24 struct backlight_device *bldev; 25 struct platform_device *pdev; 26 struct pwm_channel pwmc; 27 int gpio_on; 28}; 29 30static int atmel_pwm_bl_set_intensity(struct backlight_device *bd) 31{ 32 struct atmel_pwm_bl *pwmbl = bl_get_data(bd); 33 int intensity = bd->props.brightness; 34 int pwm_duty; 35 36 if (bd->props.power != FB_BLANK_UNBLANK) 37 intensity = 0; 38 if (bd->props.fb_blank != FB_BLANK_UNBLANK) 39 intensity = 0; 40 41 if (pwmbl->pdata->pwm_active_low) 42 pwm_duty = pwmbl->pdata->pwm_duty_min + intensity; 43 else 44 pwm_duty = pwmbl->pdata->pwm_duty_max - intensity; 45 46 if (pwm_duty > pwmbl->pdata->pwm_duty_max) 47 pwm_duty = pwmbl->pdata->pwm_duty_max; 48 if (pwm_duty < pwmbl->pdata->pwm_duty_min) 49 pwm_duty = pwmbl->pdata->pwm_duty_min; 50 51 if (!intensity) { 52 if (pwmbl->gpio_on != -1) { 53 gpio_set_value(pwmbl->gpio_on, 54 0 ^ pwmbl->pdata->on_active_low); 55 } 56 pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); 57 pwm_channel_disable(&pwmbl->pwmc); 58 } else { 59 pwm_channel_enable(&pwmbl->pwmc); 60 pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); 61 if (pwmbl->gpio_on != -1) { 62 gpio_set_value(pwmbl->gpio_on, 63 1 ^ pwmbl->pdata->on_active_low); 64 } 65 } 66 67 return 0; 68} 69 70static int atmel_pwm_bl_get_intensity(struct backlight_device *bd) 71{ 72 struct atmel_pwm_bl *pwmbl = bl_get_data(bd); 73 u8 intensity; 74 75 if (pwmbl->pdata->pwm_active_low) { 76 intensity = pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY) - 77 pwmbl->pdata->pwm_duty_min; 78 } else { 79 intensity = pwmbl->pdata->pwm_duty_max - 80 pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY); 81 } 82 83 return intensity; 84} 85 86static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl) 87{ 88 unsigned long pwm_rate = pwmbl->pwmc.mck; 89 unsigned long prescale = DIV_ROUND_UP(pwm_rate, 90 (pwmbl->pdata->pwm_frequency * 91 pwmbl->pdata->pwm_compare_max)) - 1; 92 93 /* 94 * Prescale must be power of two and maximum 0xf in size because of 95 * hardware limit. PWM speed will be: 96 * PWM module clock speed / (2 ^ prescale). 97 */ 98 prescale = fls(prescale); 99 if (prescale > 0xf) 100 prescale = 0xf; 101 102 pwm_channel_writel(&pwmbl->pwmc, PWM_CMR, prescale); 103 pwm_channel_writel(&pwmbl->pwmc, PWM_CDTY, 104 pwmbl->pdata->pwm_duty_min + 105 pwmbl->bldev->props.brightness); 106 pwm_channel_writel(&pwmbl->pwmc, PWM_CPRD, 107 pwmbl->pdata->pwm_compare_max); 108 109 dev_info(&pwmbl->pdev->dev, "Atmel PWM backlight driver " 110 "(%lu Hz)\n", pwmbl->pwmc.mck / 111 pwmbl->pdata->pwm_compare_max / 112 (1 << prescale)); 113 114 return pwm_channel_enable(&pwmbl->pwmc); 115} 116 117static const struct backlight_ops atmel_pwm_bl_ops = { 118 .get_brightness = atmel_pwm_bl_get_intensity, 119 .update_status = atmel_pwm_bl_set_intensity, 120}; 121 122static int atmel_pwm_bl_probe(struct platform_device *pdev) 123{ 124 struct backlight_properties props; 125 const struct atmel_pwm_bl_platform_data *pdata; 126 struct backlight_device *bldev; 127 struct atmel_pwm_bl *pwmbl; 128 int retval; 129 130 pwmbl = kzalloc(sizeof(struct atmel_pwm_bl), GFP_KERNEL); 131 if (!pwmbl) 132 return -ENOMEM; 133 134 pwmbl->pdev = pdev; 135 136 pdata = pdev->dev.platform_data; 137 if (!pdata) { 138 retval = -ENODEV; 139 goto err_free_mem; 140 } 141 142 if (pdata->pwm_compare_max < pdata->pwm_duty_max || 143 pdata->pwm_duty_min > pdata->pwm_duty_max || 144 pdata->pwm_frequency == 0) { 145 retval = -EINVAL; 146 goto err_free_mem; 147 } 148 149 pwmbl->pdata = pdata; 150 pwmbl->gpio_on = pdata->gpio_on; 151 152 retval = pwm_channel_alloc(pdata->pwm_channel, &pwmbl->pwmc); 153 if (retval) 154 goto err_free_mem; 155 156 if (pwmbl->gpio_on != -1) { 157 retval = gpio_request(pwmbl->gpio_on, "gpio_atmel_pwm_bl"); 158 if (retval) { 159 pwmbl->gpio_on = -1; 160 goto err_free_pwm; 161 } 162 163 /* Turn display off by default. */ 164 retval = gpio_direction_output(pwmbl->gpio_on, 165 0 ^ pdata->on_active_low); 166 if (retval) 167 goto err_free_gpio; 168 } 169 170 memset(&props, 0, sizeof(struct backlight_properties)); 171 props.max_brightness = pdata->pwm_duty_max - pdata->pwm_duty_min; 172 bldev = backlight_device_register("atmel-pwm-bl", &pdev->dev, pwmbl, 173 &atmel_pwm_bl_ops, &props); 174 if (IS_ERR(bldev)) { 175 retval = PTR_ERR(bldev); 176 goto err_free_gpio; 177 } 178 179 pwmbl->bldev = bldev; 180 181 platform_set_drvdata(pdev, pwmbl); 182 183 /* Power up the backlight by default at middle intesity. */ 184 bldev->props.power = FB_BLANK_UNBLANK; 185 bldev->props.brightness = bldev->props.max_brightness / 2; 186 187 retval = atmel_pwm_bl_init_pwm(pwmbl); 188 if (retval) 189 goto err_free_bl_dev; 190 191 atmel_pwm_bl_set_intensity(bldev); 192 193 return 0; 194 195err_free_bl_dev: 196 platform_set_drvdata(pdev, NULL); 197 backlight_device_unregister(bldev); 198err_free_gpio: 199 if (pwmbl->gpio_on != -1) 200 gpio_free(pwmbl->gpio_on); 201err_free_pwm: 202 pwm_channel_free(&pwmbl->pwmc); 203err_free_mem: 204 kfree(pwmbl); 205 return retval; 206} 207 208static int __exit atmel_pwm_bl_remove(struct platform_device *pdev) 209{ 210 struct atmel_pwm_bl *pwmbl = platform_get_drvdata(pdev); 211 212 if (pwmbl->gpio_on != -1) { 213 gpio_set_value(pwmbl->gpio_on, 0); 214 gpio_free(pwmbl->gpio_on); 215 } 216 pwm_channel_disable(&pwmbl->pwmc); 217 pwm_channel_free(&pwmbl->pwmc); 218 backlight_device_unregister(pwmbl->bldev); 219 platform_set_drvdata(pdev, NULL); 220 kfree(pwmbl); 221 222 return 0; 223} 224 225static struct platform_driver atmel_pwm_bl_driver = { 226 .driver = { 227 .name = "atmel-pwm-bl", 228 }, 229 /* REVISIT add suspend() and resume() */ 230 .remove = __exit_p(atmel_pwm_bl_remove), 231}; 232 233static int __init atmel_pwm_bl_init(void) 234{ 235 return platform_driver_probe(&atmel_pwm_bl_driver, atmel_pwm_bl_probe); 236} 237module_init(atmel_pwm_bl_init); 238 239static void __exit atmel_pwm_bl_exit(void) 240{ 241 platform_driver_unregister(&atmel_pwm_bl_driver); 242} 243module_exit(atmel_pwm_bl_exit); 244 245MODULE_AUTHOR("Hans-Christian egtvedt <hans-christian.egtvedt@atmel.com>"); 246MODULE_DESCRIPTION("Atmel PWM backlight driver"); 247MODULE_LICENSE("GPL"); 248