blob: 7ff5fda6676b3737bbae86133253f1e526c2f049 [file] [log] [blame]
/*
* lm3695_bl.c - Linux kernel module for National Semiconductor
* LM3695 backlight driver
*
* Copyright (C) 2014 Nest Labs, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
* Required properties:
* compatible : should be "national,lm3695
* reg : i2c slave address (0x63)
* enable-gpio : hardware enable gpio.
*
* Optional properties:
* max-brightness : max current allowed through each channel.
* iLED = 50uA x 1.003040572^brightness. Defaults to 2047
* default-brightness : Current set on driver probe. defaults to
* max-brigtness/2
* ramp-rate : time in microseconds between brightness level steps
* during ramping. Defaults to 500. 0 means no ramping.
* clock-divider : 0-9, number of dividers to apply to 62.5kHz
* dithering clk. e.g. 3 = (2^3) divider = 7.8khz
* single-string : (bool) use only string 1
* boost-freq : (bool) double boost switcher freqency from 500kHz to 1MHz
* freq-shift : (bool) shift boost and dithering freq by -12%.
* high-voltage : (bool) Use 21v OVP instead of 16v OVP
*
* example:
*
* lm3695@63{
* compatible= "national,lm3695";
* reg = <0x68>;
* max-brightness = <2047>;
* default-brightness = <2000>;
* single-string;
* ramp-rate = <4000>;
* enable-gpio = <&gpio2 14 GPIO_ACTIVE_HIGH>;
* }
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/backlight.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#define LM3695_DEV "lcd-backlight"
#define LM3695_NAME "lm3695_bl"
#define LM3695_MAX_BRIGHTNESS ((1 << 11) - 1)
#define MICROSEC_PER_MILLISEC (1000)
#define LM3695_GEN_PURPOSE_REG 0x10
#define LM3695_BOOST_FREQ_SHIFT_M (1<<6)
#define LM3695_BOOST_FREQ_SELECT_M (1<<5)
#define LM3695_OVP_SELECT_M (1<<4)
#define LM3695_STRING_MODE_M (1<<3)
#define LM3695_BRIGHTNESS_REG_EN_M (1<<2)
#define LM3695_RAMP_DISABLE_M (1<<1)
#define LM3695_CHIP_EN_M (1<<0)
#define LM3695_DITHER_FREQ_REG 0x11
#define LM3695_DITHER_FREQ_DIV_M 0b1111
#define LM3695_RAMP_RATE_REG 0x12
#define LM3695_RAMP_RATE_0_MS 0b1111
#define LM3695_RAMP_RATE_0_125_MS 0b0000
#define LM3695_RAMP_RATE_0_252_MS 0b0001
#define LM3695_RAMP_RATE_0_5_MS 0b0010
#define LM3695_RAMP_RATE_1_MS 0b0011
#define LM3695_RAMP_RATE_2_MS 0b0100
#define LM3695_RAMP_RATE_4_MS 0b0101
#define LM3695_RAMP_RATE_8_MS 0b0110
#define LM3695_RAMP_RATE_16_MS 0b0111
#define LM3695_RAMP_RATE_32_MS 0b1000
#define LM3695_RAMP_RATE_64_MS 0b1001
#define LM3695_RAMP_RATE_128_MS 0b1010
static const int rates[] = {0, 125, 252, 500, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000};
#define LM3695_BRIGHTNESS_L_REG 0x13
#define LM3695_BRIGHTNESS_L_M 0b111
#define LM3695_BRIGHTNESS_L_O 0
#define LM3695_BRIGHTNESS_L_S(x) (((x) & LM3695_BRIGHTNESS_L_M) >> LM3695_BRIGHTNESS_L_O)
#define LM3695_BRIGHTNESS_H_REG 0x14
#define LM3695_BRIGHTNESS_H_M 0xFF
#define LM3695_BRIGHTNESS_H_O 3
#define LM3695_BRIGHTNESS_H_S(x) ((((x) >> LM3695_BRIGHTNESS_H_O )& LM3695_BRIGHTNESS_H_M))
int default_brightness = 0;
module_param(default_brightness, int, 0664);
MODULE_PARM_DESC(default_brightness, "default brightness to ramp up to at probe");
struct lm3695_data{
struct i2c_client *client;
int max_current;
int hwen_gpio;
uint16_t max_brightness;
uint16_t default_brightness;
uint16_t current_brightness;
uint16_t next_brightness;
uint8_t ramp_rise_rate;
uint8_t ramp_fall_rate;
uint8_t dither_freq;
bool boost_freq;
bool boost_shift;
bool single_string;
bool high_voltage;
bool enabled;
};
static ssize_t store_ramp_time(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len);
static ssize_t show_ramp_time(struct device *dev,
struct device_attribute *attr, char *buf);
DEVICE_ATTR(ramp_rise_time, S_IRUGO | S_IWUSR, show_ramp_time, store_ramp_time);
DEVICE_ATTR(ramp_fall_time, S_IRUGO | S_IWUSR, show_ramp_time, store_ramp_time);
static struct attribute *lm3695_bl_attributes[] = {
&dev_attr_ramp_rise_time.attr,
&dev_attr_ramp_fall_time.attr,
NULL
};
static struct attribute_group lm3695_bl_attribute_group = {
.attrs = lm3695_bl_attributes
};
/*
*Converts a full ramp transition time in milliseconds to a ramp rate.
*
*e.g. 1000 msec ramp: 1000 msec / 2048 ticks = .488 ticks/msec
* which is then rounded to the closest tick rate that will
* complete the transition in AT LEAST "time" milliseconds.
*/
uint8_t lm3695_time_to_ramp_rate(uint32_t time)
{
uint8_t rate;
uint32_t usec_rate = ( (time*MICROSEC_PER_MILLISEC) / LM3695_MAX_BRIGHTNESS);
switch (usec_rate) {
case 0:
rate = LM3695_RAMP_RATE_0_MS;
break;
case 1 ... 125:
rate = LM3695_RAMP_RATE_0_125_MS;
break;
case 126 ... 252:
rate = LM3695_RAMP_RATE_0_252_MS;
break;
case 253 ... 500:
rate = LM3695_RAMP_RATE_0_5_MS;
break;
case 501 ... 1000:
rate = LM3695_RAMP_RATE_1_MS;
break;
case 1001 ... 2000:
rate = LM3695_RAMP_RATE_2_MS;
break;
case 2001 ... 4000:
rate = LM3695_RAMP_RATE_4_MS;
break;
case 4001 ... 8000:
rate = LM3695_RAMP_RATE_8_MS;
break;
case 8001 ... 16000:
rate = LM3695_RAMP_RATE_16_MS;
break;
case 16001 ... 32000:
rate = LM3695_RAMP_RATE_32_MS;
break;
case 32001 ... 64000:
rate = LM3695_RAMP_RATE_64_MS;
break;
default:
rate = LM3695_RAMP_RATE_128_MS;
}
return rate;
}
static ssize_t store_ramp_time(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct backlight_device *bl = to_backlight_device(dev);
struct lm3695_data *drvdata = bl_get_data(bl);
unsigned long time;
if (kstrtoul(buf, 0, &time))
return -EINVAL;
if (attr == &dev_attr_ramp_rise_time)
drvdata->ramp_rise_rate = lm3695_time_to_ramp_rate(time);
else if (attr == &dev_attr_ramp_fall_time)
drvdata->ramp_fall_rate = lm3695_time_to_ramp_rate(time);
else
return -EINVAL;
return len;
}
/*
*Returns ramp time in milliseconds for a full transition.
*
*e.g. 4 msec/tick rate: 4000 usec * 2048 = 8192 msec
*
*/
static ssize_t show_ramp_time(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bl = to_backlight_device(dev);
struct lm3695_data *drvdata = bl_get_data(bl);
uint8_t value;
if (attr == &dev_attr_ramp_rise_time)
value = drvdata->ramp_rise_rate;
else if (attr == &dev_attr_ramp_fall_time)
value = drvdata->ramp_fall_rate;
else
return -EINVAL;
if (value == LM3695_RAMP_RATE_0_MS)
value = 0;
else
value += 1;
return sprintf(buf, "%d\n", (LM3695_MAX_BRIGHTNESS*rates[value])/MICROSEC_PER_MILLISEC);
}
int lm3695_configure_device(struct lm3695_data *drvdata, bool enable)
{
uint8_t data;
uint8_t rate;
/*send default data to chip*/
if (enable)
{
if (drvdata->next_brightness < drvdata->current_brightness)
rate = drvdata->ramp_fall_rate;
else
rate = drvdata->ramp_rise_rate;
gpio_set_value(drvdata->hwen_gpio, 1);
drvdata->enabled = true;
data = LM3695_CHIP_EN_M;
if (drvdata->boost_freq)
data |= LM3695_BOOST_FREQ_SELECT_M;
if (!drvdata->boost_shift) /*default to boost*/
data |= LM3695_BOOST_FREQ_SHIFT_M;
if (rate == LM3695_RAMP_RATE_0_MS)
data |= LM3695_RAMP_DISABLE_M;
if (drvdata->single_string)
data |= LM3695_STRING_MODE_M;
if (drvdata->high_voltage)
data |= LM3695_OVP_SELECT_M;
data |= LM3695_BRIGHTNESS_REG_EN_M;
i2c_smbus_write_byte_data(drvdata->client, LM3695_GEN_PURPOSE_REG, data);
i2c_smbus_write_byte_data(drvdata->client, LM3695_GEN_PURPOSE_REG, data);
data = drvdata->dither_freq;
i2c_smbus_write_byte_data(drvdata->client, LM3695_DITHER_FREQ_REG, data);
data = rate;
i2c_smbus_write_byte_data(drvdata->client, LM3695_RAMP_RATE_REG, data);
} else {
if (drvdata->enabled)
{
data = 0;
i2c_smbus_write_byte_data(drvdata->client, LM3695_GEN_PURPOSE_REG, data);
gpio_set_value(drvdata->hwen_gpio, 0);
drvdata->enabled = false;
}
}
return 0;
}
int lm3695_update_status(struct backlight_device * bl)
{
struct lm3695_data *drvdata = bl_get_data(bl);
bool enabled = 1;
drvdata->next_brightness = bl->props.brightness;
if (drvdata->next_brightness > drvdata->max_brightness)
drvdata->next_brightness = drvdata->max_brightness;
if (bl->props.power != FB_BLANK_UNBLANK) {
enabled = 0;
}
if (bl->props.fb_blank != FB_BLANK_UNBLANK) {
enabled = 0;
}
lm3695_configure_device(drvdata, enabled);
i2c_smbus_write_byte_data(drvdata->client, LM3695_BRIGHTNESS_L_REG,
LM3695_BRIGHTNESS_L_S(drvdata->next_brightness));
i2c_smbus_write_byte_data(drvdata->client, LM3695_BRIGHTNESS_H_REG,
LM3695_BRIGHTNESS_H_S(drvdata->next_brightness));
drvdata->current_brightness = drvdata->next_brightness;
return 0;
}
int lm3695_get_brightness(struct backlight_device *bl)
{
return bl->props.brightness;
}
static struct backlight_ops lm3695_backlight_ops = {
.update_status = lm3695_update_status,
.get_brightness = lm3695_get_brightness,
};
int lm3695_get_devtree_pdata(struct i2c_client *client, struct lm3695_data *drvdata){
const int *prop;
const char *fb_dev;
int retval = 0;
prop = of_get_property(client->dev.of_node, "max-brightness", NULL);
drvdata->max_brightness = prop ? (be32_to_cpu(*prop)) : LM3695_MAX_BRIGHTNESS;
prop = of_get_property(client->dev.of_node, "default-brightness", NULL);
drvdata->default_brightness = prop ? (be32_to_cpu(*prop)) : drvdata->max_brightness / 2;
drvdata->current_brightness = drvdata->default_brightness;
drvdata->next_brightness = drvdata->default_brightness;
retval = of_property_read_string(client->dev.of_node, "fb-device", &fb_dev);
if (!retval)
{
if(driver_find(fb_dev, &platform_bus_type) == NULL)
{
dev_warn(&client->dev, "Could not find %s driver\n", fb_dev);
retval = -EPROBE_DEFER;
goto done;
}
}
else
{
retval = 0;
dev_info(&client->dev, "No specified wait-for framebuffer. Enabling now");
}
if(default_brightness != 0)
{
/*override default brightness parameter if uboot has one for us*/
drvdata->default_brightness = default_brightness;
dev_info(&client->dev, "Using cmdline parameter %d for brightness", default_brightness);
}
drvdata->hwen_gpio = of_get_named_gpio(client->dev.of_node, "enable-gpio", 0);
prop = of_get_property(client->dev.of_node, "ramp-rise-time", NULL);
if (prop)
{
drvdata->ramp_rise_rate = lm3695_time_to_ramp_rate(be32_to_cpu(*prop));
}
prop = of_get_property(client->dev.of_node, "ramp-fall-time", NULL);
if (prop)
{
drvdata->ramp_fall_rate = lm3695_time_to_ramp_rate(be32_to_cpu(*prop));
}
else
{
drvdata->ramp_rise_rate = LM3695_RAMP_RATE_0_MS;
drvdata->ramp_fall_rate = LM3695_RAMP_RATE_0_MS;
}
prop = of_get_property(client->dev.of_node, "clock-divider", NULL);
if (prop && (be32_to_cpu(*prop) <= 0b1001) && (*prop >= 0))
{
drvdata->dither_freq = be32_to_cpu(*prop);
}
else
{
drvdata->dither_freq = 0b11;
}
drvdata->boost_freq = of_property_read_bool(client->dev.of_node, "boost-freq");
drvdata->boost_shift = of_property_read_bool(client->dev.of_node, "freq-shift");
drvdata->single_string = of_property_read_bool(client->dev.of_node, "single-string");
drvdata->high_voltage = of_property_read_bool(client->dev.of_node, "high-voltage");
done:
return retval;
}
static int lm3695_probe(struct i2c_client *client,
const struct i2c_device_id *device)
{
struct lm3695_data *drvdata;
struct platform_device *pdev = to_platform_device(&client->dev);
struct backlight_device *bl;
struct backlight_properties props;
int status;
drvdata = devm_kzalloc(&client->dev,sizeof(struct lm3695_data), GFP_KERNEL);
status = lm3695_get_devtree_pdata(client, drvdata);
if (status)
{
goto cleanup;
}
drvdata->client = client;
i2c_set_clientdata(client, drvdata);
status = devm_gpio_request_one(&client->dev, drvdata->hwen_gpio, GPIOF_OUT_INIT_LOW, "lm3695 enable");
if (status)
{
dev_err(&client->dev, "Could not request GPIO %u: %d\n", drvdata->hwen_gpio, status);
goto cleanup;
}
drvdata->enabled = false;
memset(&props, 0, sizeof(struct backlight_properties));
props.max_brightness = LM3695_MAX_BRIGHTNESS;
props.type = BACKLIGHT_FIRMWARE;
bl = backlight_device_register(dev_name(&client->dev), &client->dev,
drvdata, &lm3695_backlight_ops, &props);
if (IS_ERR(bl))
{
dev_err(&client->dev, "Could not register bl: %ld\n", PTR_ERR(bl));
goto cleanup;
}
status = sysfs_create_group(&bl->dev.kobj, &lm3695_bl_attribute_group);
if (status < 0) {
dev_err(&pdev->dev, "failed to create sysfs attributes\n");
goto unregister;
}
bl->props.max_brightness = drvdata->max_brightness;
bl->props.brightness = drvdata->default_brightness;
backlight_update_status(bl);
platform_set_drvdata(pdev, bl);
return 0;
unregister:
backlight_device_unregister(bl);
cleanup:
return status;
}
static int lm3695_remove(struct i2c_client *client)
{
struct platform_device *pdev = to_platform_device(&client->dev);
struct backlight_device *bl = platform_get_drvdata(pdev);
struct lm3695_data *drvdata = bl_get_data(bl);
lm3695_configure_device(drvdata, 0);
backlight_device_unregister(bl);
sysfs_remove_group(&bl->dev.kobj, &lm3695_bl_attribute_group);
return 0;
}
static void lm3695_shutdown(struct i2c_client *client)
{
struct platform_device *pdev = to_platform_device(&client->dev);
struct backlight_device *bl = platform_get_drvdata(pdev);
struct lm3695_data *drvdata = bl_get_data(bl);
lm3695_configure_device(drvdata, 0);
}
static int lm3695_suspend(struct device *dev){
struct platform_device *pdev = to_platform_device(dev);
struct backlight_device *bl = platform_get_drvdata(pdev);
struct lm3695_data *drvdata = bl_get_data(bl);
lm3695_configure_device(drvdata, 0);
return 0;
}
static int lm3695_resume(struct device *dev){
struct platform_device *pdev = to_platform_device(dev);
struct backlight_device *bl = platform_get_drvdata(pdev);
backlight_update_status(bl);
return 0;
}
static const struct i2c_device_id lm3695_id[] = {
{LM3695_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, lm3695_id);
static struct dev_pm_ops lm3695_pm_ops = {
.suspend = lm3695_suspend,
.resume = lm3695_resume,
};
#ifdef CONFIG_OF
static const struct of_device_id of_lm3695_bl_match[] = {
{ .compatible = "national,lm3695", },
{},
};
#endif
MODULE_DEVICE_TABLE(of, of_lm3695_bl_match);
static struct i2c_driver lm3695_i2c_driver = {
.probe = lm3695_probe,
.remove = lm3695_remove,
.id_table = lm3695_id,
.shutdown = lm3695_shutdown,
.driver = {
.name = LM3695_NAME,
.owner = THIS_MODULE,
.pm = &lm3695_pm_ops,
.of_match_table = of_match_ptr(of_lm3695_bl_match),
},
};
static int __init lm3695_init(void)
{
return i2c_add_driver(&lm3695_i2c_driver);
}
static void __exit lm3695_exit(void)
{
i2c_del_driver(&lm3695_i2c_driver);
}
module_init(lm3695_init);
module_exit(lm3695_exit);
MODULE_DESCRIPTION("i2c driver for LM3695 backlight driver");
MODULE_LICENSE("GPLv2");
MODULE_AUTHOR("Andrew LeCain<alecain@nestlabs.com>");