88pm860x_bl.c 6.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Backlight driver for Marvell Semiconductor 88PM8606
 *
 * Copyright (C) 2009 Marvell International Ltd.
 *	Haojian Zhuang <haojian.zhuang@marvell.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
15
#include <linux/slab.h>
16 17 18 19
#include <linux/fb.h>
#include <linux/i2c.h>
#include <linux/backlight.h>
#include <linux/mfd/88pm860x.h>
20
#include <linux/module.h>
21 22 23 24

#define MAX_BRIGHTNESS		(0xFF)
#define MIN_BRIGHTNESS		(0)

25
#define CURRENT_BITMASK		(0x1F << 1)
26 27 28 29 30 31 32 33

struct pm860x_backlight_data {
	struct pm860x_chip *chip;
	struct i2c_client *i2c;
	int	current_brightness;
	int	port;
	int	pwm;
	int	iset;
34 35 36
	int	reg_duty_cycle;
	int	reg_always_on;
	int	reg_current;
37 38
};

39 40 41 42 43 44
static int backlight_power_set(struct pm860x_chip *chip, int port,
		int on)
{
	int ret = -EINVAL;

	switch (port) {
45
	case 0:
46 47 48
		ret = on ? pm8606_osc_enable(chip, WLED1_DUTY) :
			pm8606_osc_disable(chip, WLED1_DUTY);
		break;
49
	case 1:
50 51 52
		ret = on ? pm8606_osc_enable(chip, WLED2_DUTY) :
			pm8606_osc_disable(chip, WLED2_DUTY);
		break;
53
	case 2:
54 55 56 57 58 59 60
		ret = on ? pm8606_osc_enable(chip, WLED3_DUTY) :
			pm8606_osc_disable(chip, WLED3_DUTY);
		break;
	}
	return ret;
}

61 62 63 64 65 66 67 68 69 70 71 72
static int pm860x_backlight_set(struct backlight_device *bl, int brightness)
{
	struct pm860x_backlight_data *data = bl_get_data(bl);
	struct pm860x_chip *chip = data->chip;
	unsigned char value;
	int ret;

	if (brightness > MAX_BRIGHTNESS)
		value = MAX_BRIGHTNESS;
	else
		value = brightness;

73 74 75
	if (brightness)
		backlight_power_set(chip, data->port, 1);

76
	ret = pm860x_reg_write(data->i2c, data->reg_duty_cycle, value);
77 78 79 80 81
	if (ret < 0)
		goto out;

	if ((data->current_brightness == 0) && brightness) {
		if (data->iset) {
82
			ret = pm860x_set_bits(data->i2c, data->reg_current,
83
					      CURRENT_BITMASK, data->iset);
84 85 86 87 88 89 90 91 92 93 94
			if (ret < 0)
				goto out;
		}
		if (data->pwm) {
			ret = pm860x_set_bits(data->i2c, PM8606_PWM,
					      PM8606_PWM_FREQ_MASK, data->pwm);
			if (ret < 0)
				goto out;
		}
		if (brightness == MAX_BRIGHTNESS) {
			/* set WLED_ON bit as 100% */
95
			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
96 97 98 99 100
					      PM8606_WLED_ON, PM8606_WLED_ON);
		}
	} else {
		if (brightness == MAX_BRIGHTNESS) {
			/* set WLED_ON bit as 100% */
101
			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
102 103 104
					      PM8606_WLED_ON, PM8606_WLED_ON);
		} else {
			/* clear WLED_ON bit since it's not 100% */
105
			ret = pm860x_set_bits(data->i2c, data->reg_always_on,
106 107 108 109 110 111
					      PM8606_WLED_ON, 0);
		}
	}
	if (ret < 0)
		goto out;

112 113 114
	if (brightness == 0)
		backlight_power_set(chip, data->port, 0);

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
	dev_dbg(chip->dev, "set brightness %d\n", value);
	data->current_brightness = value;
	return 0;
out:
	dev_dbg(chip->dev, "set brightness %d failure with return "
		"value:%d\n", value, ret);
	return ret;
}

static int pm860x_backlight_update_status(struct backlight_device *bl)
{
	int brightness = bl->props.brightness;

	if (bl->props.power != FB_BLANK_UNBLANK)
		brightness = 0;

	if (bl->props.fb_blank != FB_BLANK_UNBLANK)
		brightness = 0;

	if (bl->props.state & BL_CORE_SUSPENDED)
		brightness = 0;

	return pm860x_backlight_set(bl, brightness);
}

static int pm860x_backlight_get_brightness(struct backlight_device *bl)
{
	struct pm860x_backlight_data *data = bl_get_data(bl);
	struct pm860x_chip *chip = data->chip;
	int ret;

146
	ret = pm860x_reg_read(data->i2c, data->reg_duty_cycle);
147 148 149 150 151 152 153 154 155
	if (ret < 0)
		goto out;
	data->current_brightness = ret;
	dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
	return data->current_brightness;
out:
	return -EINVAL;
}

L
Lionel Debroux 已提交
156
static const struct backlight_ops pm860x_backlight_ops = {
157 158 159 160 161 162 163 164
	.options	= BL_CORE_SUSPENDRESUME,
	.update_status	= pm860x_backlight_update_status,
	.get_brightness	= pm860x_backlight_get_brightness,
};

static int pm860x_backlight_probe(struct platform_device *pdev)
{
	struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
165
	struct pm860x_backlight_pdata *pdata = pdev->dev.platform_data;
166 167 168
	struct pm860x_backlight_data *data;
	struct backlight_device *bl;
	struct resource *res;
169
	struct backlight_properties props;
170
	char name[MFD_NAME_SIZE];
171
	int ret = 0;
172

173 174
	data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data),
			    GFP_KERNEL);
175 176
	if (data == NULL)
		return -ENOMEM;
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "duty cycle");
	if (!res) {
		dev_err(&pdev->dev, "No REG resource for duty cycle\n");
		ret = -ENXIO;
		goto out;
	}
	data->reg_duty_cycle = res->start;
	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "always on");
	if (!res) {
		dev_err(&pdev->dev, "No REG resorce for always on\n");
		ret = -ENXIO;
		goto out;
	}
	data->reg_always_on = res->start;
	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "current");
	if (!res) {
		dev_err(&pdev->dev, "No REG resource for current\n");
		ret = -ENXIO;
		goto out;
	}
	data->reg_current = res->start;

	memset(name, 0, MFD_NAME_SIZE);
	sprintf(name, "backlight-%d", pdev->id);
	data->port = pdev->id;
202 203 204 205
	data->chip = chip;
	data->i2c = (chip->id == CHIP_PM8606) ? chip->client	\
			: chip->companion;
	data->current_brightness = MAX_BRIGHTNESS;
206 207 208
	if (pdata) {
		data->pwm = pdata->pwm;
		data->iset = pdata->iset;
209 210
	}

211
	memset(&props, 0, sizeof(struct backlight_properties));
M
Matthew Garrett 已提交
212
	props.type = BACKLIGHT_RAW;
213
	props.max_brightness = MAX_BRIGHTNESS;
214
	bl = backlight_device_register(name, &pdev->dev, data,
215
					&pm860x_backlight_ops, &props);
216 217 218 219 220 221 222 223 224 225 226
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		return PTR_ERR(bl);
	}
	bl->props.brightness = MAX_BRIGHTNESS;

	platform_set_drvdata(pdev, bl);

	/* read current backlight */
	ret = pm860x_backlight_get_brightness(bl);
	if (ret < 0)
227
		goto out_brt;
228 229 230

	backlight_update_status(bl);
	return 0;
231
out_brt:
232
	backlight_device_unregister(bl);
233 234
out:
	devm_kfree(&pdev->dev, data);
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
	return ret;
}

static int pm860x_backlight_remove(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);

	backlight_device_unregister(bl);
	return 0;
}

static struct platform_driver pm860x_backlight_driver = {
	.driver		= {
		.name	= "88pm860x-backlight",
		.owner	= THIS_MODULE,
	},
	.probe		= pm860x_backlight_probe,
	.remove		= pm860x_backlight_remove,
};

255
module_platform_driver(pm860x_backlight_driver);
256 257 258 259 260

MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:88pm860x-backlight");