Commit 0512c04a authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds

Pull LED subsystem update from Bryan Wu.

* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds: (61 commits)
  leds: leds-sunfire: use dev_err()/pr_err() instead of printk()
  leds: 88pm860x: Add missing of_node_put()
  leds: tca6507: Use of_get_child_count()
  leds: leds-pwm: make it depend on PWM and not HAVE_PWM
  Documentation: leds: update LP55xx family devices
  leds-lp55xx: fix problem on removing LED attributes
  leds-lp5521/5523: add author and copyright description
  leds-lp5521/5523: use new lp55xx common header
  leds-lp55xx: clean up headers
  leds-lp55xx: clean up definitions
  leds-lp55xx: clean up unused data and functions
  leds-lp55xx: clean up _remove()
  leds-lp55xx: add new function for removing device attribtues
  leds-lp55xx: code refactoring on selftest function
  leds-lp55xx: use common device attribute driver function
  leds-lp55xx: support device specific attributes
  leds-lp5523: use generic firmware interface
  leds-lp5521: use generic firmware interface
  leds-lp55xx: support firmware interface
  leds-lp55xx: add new lp55xx_register_sysfs() for the firmware interface
  ...
parents 5115f3c1 4b07c5d5
LED connected to PWM
Required properties:
- compatible : should be "pwm-leds".
Each LED is represented as a sub-node of the pwm-leds device. Each
node's name represents the name of the corresponding LED.
LED sub-node properties:
- pwms : PWM property to point to the PWM device (phandle)/port (id) and to
specify the period time to be used: <&phandle id period_ns>;
- pwm-names : (optional) Name to be used by the PWM subsystem for the PWM device
For the pwms and pwm-names property please refer to:
Documentation/devicetree/bindings/pwm/pwm.txt
- max-brightness : Maximum brightness possible for the LED
- label : (optional)
see Documentation/devicetree/bindings/leds/common.txt
- linux,default-trigger : (optional)
see Documentation/devicetree/bindings/leds/common.txt
Example:
twl_pwm: pwm {
/* provides two PWMs (id 0, 1 for PWM1 and PWM2) */
compatible = "ti,twl6030-pwm";
#pwm-cells = <2>;
};
twl_pwmled: pwmled {
/* provides one PWM (id 0 for Charing indicator LED) */
compatible = "ti,twl6030-pwmled";
#pwm-cells = <2>;
};
pwmleds {
compatible = "pwm-leds";
kpad {
label = "omap4::keypad";
pwms = <&twl_pwm 0 7812500>;
max-brightness = <127>;
};
charging {
label = "omap4:green:chrg";
pwms = <&twl_pwmled 0 7812500>;
max-brightness = <255>;
};
};
LEDs conected to tca6507
Required properties:
- compatible : should be : "ti,tca6507".
Each led is represented as a sub-node of the ti,tca6507 device.
LED sub-node properties:
- label : (optional) see Documentation/devicetree/bindings/leds/common.txt
- reg : number of LED line (could be from 0 to 6)
- linux,default-trigger : (optional)
see Documentation/devicetree/bindings/leds/common.txt
Examples:
tca6507@45 {
compatible = "ti,tca6507";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x45>;
led0: red-aux@0 {
label = "red:aux";
reg = <0x0>;
};
led1: green-aux@1 {
label = "green:aux";
reg = <0x5>;
linux,default-trigger = "default-on";
};
};
......@@ -6,5 +6,7 @@ leds-lp5521.txt
- notes on how to use the leds-lp5521 driver.
leds-lp5523.txt
- notes on how to use the leds-lp5523 driver.
leds-lp55xx.txt
- description about lp55xx common driver.
leds-lm3556.txt
- notes on how to use the leds-lm3556 driver.
......@@ -17,19 +17,8 @@ lp5521:channelx, where x is 0 .. 2
All three channels can be also controlled using the engine micro programs.
More details of the instructions can be found from the public data sheet.
Control interface for the engines:
x is 1 .. 3
enginex_mode : disabled, load, run
enginex_load : store program (visible only in engine load mode)
Example (start to blink the channel 2 led):
cd /sys/class/leds/lp5521:channel2/device
echo "load" > engine3_mode
echo "037f4d0003ff6000" > engine3_load
echo "run" > engine3_mode
stop the engine:
echo "disabled" > engine3_mode
LP5521 has the internal program memory for running various LED patterns.
For the details, please refer to 'firmware' section in leds-lp55xx.txt
sysfs contains a selftest entry.
The test communicates with the chip and checks that
......@@ -47,7 +36,7 @@ The name of each channel can be configurable.
If the name field is not defined, the default name will be set to 'xxxx:channelN'
(XXXX : pdata->label or i2c client name, N : channel number)
static struct lp5521_led_config lp5521_led_config[] = {
static struct lp55xx_led_config lp5521_led_config[] = {
{
.name = "red",
.chan_nr = 0,
......@@ -81,10 +70,10 @@ static void lp5521_enable(bool state)
/* Control of chip enable signal */
}
static struct lp5521_platform_data lp5521_platform_data = {
static struct lp55xx_platform_data lp5521_platform_data = {
.led_config = lp5521_led_config,
.num_channels = ARRAY_SIZE(lp5521_led_config),
.clock_mode = LP5521_CLOCK_EXT,
.clock_mode = LP55XX_CLOCK_EXT,
.setup_resources = lp5521_setup,
.release_resources = lp5521_release,
.enable = lp5521_enable,
......@@ -105,47 +94,9 @@ example of update_config :
LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT | \
LP5521_CLK_INT)
static struct lp5521_platform_data lp5521_pdata = {
static struct lp55xx_platform_data lp5521_pdata = {
.led_config = lp5521_led_config,
.num_channels = ARRAY_SIZE(lp5521_led_config),
.clock_mode = LP5521_CLOCK_INT,
.clock_mode = LP55XX_CLOCK_INT,
.update_config = LP5521_CONFIGS,
};
LED patterns : LP5521 has autonomous operation without external control.
Pattern data can be defined in the platform data.
example of led pattern data :
/* RGB(50,5,0) 500ms on, 500ms off, infinite loop */
static u8 pattern_red[] = {
0x40, 0x32, 0x60, 0x00, 0x40, 0x00, 0x60, 0x00,
};
static u8 pattern_green[] = {
0x40, 0x05, 0x60, 0x00, 0x40, 0x00, 0x60, 0x00,
};
static struct lp5521_led_pattern board_led_patterns[] = {
{
.r = pattern_red,
.g = pattern_green,
.size_r = ARRAY_SIZE(pattern_red),
.size_g = ARRAY_SIZE(pattern_green),
},
};
static struct lp5521_platform_data lp5521_platform_data = {
.led_config = lp5521_led_config,
.num_channels = ARRAY_SIZE(lp5521_led_config),
.clock_mode = LP5521_CLOCK_EXT,
.patterns = board_led_patterns,
.num_patterns = ARRAY_SIZE(board_led_patterns),
};
Then predefined led pattern(s) can be executed via the sysfs.
To start the pattern #1,
# echo 1 > /sys/bus/i2c/devices/xxxx/led_pattern
(xxxx : i2c bus & slave address)
To end the pattern,
# echo 0 > /sys/bus/i2c/devices/xxxx/led_pattern
......@@ -27,25 +27,8 @@ c) Default
If both fields are NULL, 'lp5523' is used by default.
/sys/class/leds/lp5523:channelN (N: 0 ~ 8)
The chip provides 3 engines. Each engine can control channels without
interaction from the main CPU. Details of the micro engine code can be found
from the public data sheet. Leds can be muxed to different channels.
Control interface for the engines:
x is 1 .. 3
enginex_mode : disabled, load, run
enginex_load : microcode load (visible only in load mode)
enginex_leds : led mux control (visible only in load mode)
cd /sys/class/leds/lp5523:channel2/device
echo "load" > engine3_mode
echo "9d80400004ff05ff437f0000" > engine3_load
echo "111111111" > engine3_leds
echo "run" > engine3_mode
sysfs contains a selftest entry. It measures each channel
voltage level and checks if it looks reasonable. If the level is too high,
the led is missing; if the level is too low, there is a short circuit.
LP5523 has the internal program memory for running various LED patterns.
For the details, please refer to 'firmware' section in leds-lp55xx.txt
Selftest uses always the current from the platform data.
......@@ -58,7 +41,7 @@ Example platform data:
Note - chan_nr can have values between 0 and 8.
static struct lp5523_led_config lp5523_led_config[] = {
static struct lp55xx_led_config lp5523_led_config[] = {
{
.name = "D1",
.chan_nr = 0,
......@@ -88,10 +71,10 @@ static void lp5523_enable(bool state)
/* Control chip enable signal */
}
static struct lp5523_platform_data lp5523_platform_data = {
static struct lp55xx_platform_data lp5523_platform_data = {
.led_config = lp5523_led_config,
.num_channels = ARRAY_SIZE(lp5523_led_config),
.clock_mode = LP5523_CLOCK_EXT,
.clock_mode = LP55XX_CLOCK_EXT,
.setup_resources = lp5523_setup,
.release_resources = lp5523_release,
.enable = lp5523_enable,
......
LP5521/LP5523/LP55231 Common Driver
===================================
Authors: Milo(Woogyom) Kim <milo.kim@ti.com>
Description
-----------
LP5521, LP5523/55231 have common features as below.
Register access via the I2C
Device initialization/deinitialization
Create LED class devices for multiple output channels
Device attributes for user-space interface
Program memory for running LED patterns
The LP55xx common driver provides these features using exported functions.
lp55xx_init_device() / lp55xx_deinit_device()
lp55xx_register_leds() / lp55xx_unregister_leds()
lp55xx_regsister_sysfs() / lp55xx_unregister_sysfs()
( Driver Structure Data )
In lp55xx common driver, two different data structure is used.
o lp55xx_led
control multi output LED channels such as led current, channel index.
o lp55xx_chip
general chip control such like the I2C and platform data.
For example, LP5521 has maximum 3 LED channels.
LP5523/55231 has 9 output channels.
lp55xx_chip for LP5521 ... lp55xx_led #1
lp55xx_led #2
lp55xx_led #3
lp55xx_chip for LP5523 ... lp55xx_led #1
lp55xx_led #2
.
.
lp55xx_led #9
( Chip Dependent Code )
To support device specific configurations, special structure
'lpxx_device_config' is used.
Maximum number of channels
Reset command, chip enable command
Chip specific initialization
Brightness control register access
Setting LED output current
Program memory address access for running patterns
Additional device specific attributes
( Firmware Interface )
LP55xx family devices have the internal program memory for running
various LED patterns.
This pattern data is saved as a file in the user-land or
hex byte string is written into the memory through the I2C.
LP55xx common driver supports the firmware interface.
LP55xx chips have three program engines.
To load and run the pattern, the programming sequence is following.
(1) Select an engine number (1/2/3)
(2) Mode change to load
(3) Write pattern data into selected area
(4) Mode change to run
The LP55xx common driver provides simple interfaces as below.
select_engine : Select which engine is used for running program
run_engine : Start program which is loaded via the firmware interface
firmware : Load program data
For example, run blinking pattern in engine #1 of LP5521
echo 1 > /sys/bus/i2c/devices/xxxx/select_engine
echo 1 > /sys/class/firmware/lp5521/loading
echo "4000600040FF6000" > /sys/class/firmware/lp5521/data
echo 0 > /sys/class/firmware/lp5521/loading
echo 1 > /sys/bus/i2c/devices/xxxx/run_engine
For example, run blinking pattern in engine #3 of LP55231
echo 3 > /sys/bus/i2c/devices/xxxx/select_engine
echo 1 > /sys/class/firmware/lp55231/loading
echo "9d0740ff7e0040007e00a0010000" > /sys/class/firmware/lp55231/data
echo 0 > /sys/class/firmware/lp55231/loading
echo 1 > /sys/bus/i2c/devices/xxxx/run_engine
To start blinking patterns in engine #2 and #3 simultaneously,
for idx in 2 3
do
echo $idx > /sys/class/leds/red/device/select_engine
sleep 0.1
echo 1 > /sys/class/firmware/lp5521/loading
echo "4000600040FF6000" > /sys/class/firmware/lp5521/data
echo 0 > /sys/class/firmware/lp5521/loading
done
echo 1 > /sys/class/leds/red/device/run_engine
Here is another example for LP5523.
echo 2 > /sys/bus/i2c/devices/xxxx/select_engine
echo 1 > /sys/class/firmware/lp5523/loading
echo "9d80400004ff05ff437f0000" > /sys/class/firmware/lp5523/data
echo 0 > /sys/class/firmware/lp5523/loading
echo 1 > /sys/bus/i2c/devices/xxxx/run_engine
As soon as 'loading' is set to 0, registered callback is called.
Inside the callback, the selected engine is loaded and memory is updated.
To run programmed pattern, 'run_engine' attribute should be enabled.
( 'run_engine' and 'firmware_cb' )
The sequence of running the program data is common.
But each device has own specific register addresses for commands.
To support this, 'run_engine' and 'firmware_cb' are configurable in each driver.
run_engine : Control the selected engine
firmware_cb : The callback function after loading the firmware is done.
Chip specific commands for loading and updating program memory.
......@@ -40,7 +40,7 @@
#include <sound/tpa6130a2-plat.h>
#include <media/radio-si4713.h>
#include <media/si4713.h>
#include <linux/leds-lp5523.h>
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/platform_data/tsl2563.h>
#include <linux/lis3lv02d.h>
......@@ -160,7 +160,7 @@ static struct tsl2563_platform_data rx51_tsl2563_platform_data = {
#endif
#if defined(CONFIG_LEDS_LP5523) || defined(CONFIG_LEDS_LP5523_MODULE)
static struct lp5523_led_config rx51_lp5523_led_config[] = {
static struct lp55xx_led_config rx51_lp5523_led_config[] = {
{
.name = "lp5523:kb1",
.chan_nr = 0,
......@@ -216,10 +216,10 @@ static void rx51_lp5523_enable(bool state)
gpio_set_value(RX51_LP5523_CHIP_EN_GPIO, !!state);
}
static struct lp5523_platform_data rx51_lp5523_platform_data = {
static struct lp55xx_platform_data rx51_lp5523_platform_data = {
.led_config = rx51_lp5523_led_config,
.num_channels = ARRAY_SIZE(rx51_lp5523_led_config),
.clock_mode = LP5523_CLOCK_AUTO,
.clock_mode = LP55XX_CLOCK_AUTO,
.setup_resources = rx51_lp5523_setup,
.release_resources = rx51_lp5523_release,
.enable = rx51_lp5523_enable,
......
......@@ -28,7 +28,7 @@
#include <linux/mfd/tps6105x.h>
#include <linux/mfd/abx500/ab8500-gpio.h>
#include <linux/mfd/abx500/ab8500-codec.h>
#include <linux/leds-lp5521.h>
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/input.h>
#include <linux/smsc911x.h>
#include <linux/gpio_keys.h>
......@@ -301,7 +301,7 @@ static struct tc3589x_platform_data mop500_tc35892_data = {
.irq_base = MOP500_EGPIO_IRQ_BASE,
};
static struct lp5521_led_config lp5521_pri_led[] = {
static struct lp55xx_led_config lp5521_pri_led[] = {
[0] = {
.chan_nr = 0,
.led_current = 0x2f,
......@@ -319,14 +319,14 @@ static struct lp5521_led_config lp5521_pri_led[] = {
},
};
static struct lp5521_platform_data __initdata lp5521_pri_data = {
static struct lp55xx_platform_data __initdata lp5521_pri_data = {
.label = "lp5521_pri",
.led_config = &lp5521_pri_led[0],
.num_channels = 3,
.clock_mode = LP5521_CLOCK_EXT,
.clock_mode = LP55XX_CLOCK_EXT,
};
static struct lp5521_led_config lp5521_sec_led[] = {
static struct lp55xx_led_config lp5521_sec_led[] = {
[0] = {
.chan_nr = 0,
.led_current = 0x2f,
......@@ -344,11 +344,11 @@ static struct lp5521_led_config lp5521_sec_led[] = {
},
};
static struct lp5521_platform_data __initdata lp5521_sec_data = {
static struct lp55xx_platform_data __initdata lp5521_sec_data = {
.label = "lp5521_sec",
.led_config = &lp5521_sec_led[0],
.num_channels = 3,
.clock_mode = LP5521_CLOCK_EXT,
.clock_mode = LP55XX_CLOCK_EXT,
};
static struct i2c_board_info __initdata mop500_i2c0_devices[] = {
......
......@@ -193,9 +193,18 @@ config LEDS_LP3944
To compile this driver as a module, choose M here: the
module will be called leds-lp3944.
config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521 and LP5523/55231"
depends on LEDS_LP5521 || LEDS_LP5523
select FW_LOADER
help
This option supports common operations for LP5521 and LP5523/55231
devices.
config LEDS_LP5521
tristate "LED Support for N.S. LP5521 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
help
If you say yes here you get support for the National Semiconductor
LP5521 LED driver. It is 3 channel chip with programmable engines.
......@@ -205,6 +214,7 @@ config LEDS_LP5521
config LEDS_LP5523
tristate "LED Support for TI/National LP5523/55231 LED driver chip"
depends on LEDS_CLASS && I2C
select LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI/National Semiconductor
LP5523/55231 LED driver.
......@@ -310,7 +320,7 @@ config LEDS_DAC124S085
config LEDS_PWM
tristate "PWM driven LED Support"
depends on LEDS_CLASS
depends on HAVE_PWM
depends on PWM
help
This option enables support for pwm driven LEDs
......
......@@ -23,6 +23,7 @@ obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
......
......@@ -128,8 +128,10 @@ static void pm860x_led_set(struct led_classdev *cdev,
static int pm860x_led_dt_init(struct platform_device *pdev,
struct pm860x_led *data)
{
struct device_node *nproot = pdev->dev.parent->of_node, *np;
struct device_node *nproot, *np;
int iset = 0;
nproot = of_node_get(pdev->dev.parent->of_node);
if (!nproot)
return -ENODEV;
nproot = of_find_node_by_name(nproot, "leds");
......@@ -145,6 +147,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev,
break;
}
}
of_node_put(nproot);
return 0;
}
#else
......
......@@ -187,6 +187,40 @@ static void lm3530_als_configure(struct lm3530_platform_data *pdata,
(pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT);
}
static int lm3530_led_enable(struct lm3530_data *drvdata)
{
int ret;
if (drvdata->enable)
return 0;
ret = regulator_enable(drvdata->regulator);
if (ret) {
dev_err(drvdata->led_dev.dev, "Failed to enable vin:%d\n", ret);
return ret;
}
drvdata->enable = true;
return 0;
}
static void lm3530_led_disable(struct lm3530_data *drvdata)
{
int ret;
if (!drvdata->enable)
return;
ret = regulator_disable(drvdata->regulator);
if (ret) {
dev_err(drvdata->led_dev.dev, "Failed to disable vin:%d\n",
ret);
return;
}
drvdata->enable = false;
}
static int lm3530_init_registers(struct lm3530_data *drvdata)
{
int ret = 0;
......@@ -245,15 +279,9 @@ static int lm3530_init_registers(struct lm3530_data *drvdata)
reg_val[12] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */
reg_val[13] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */
if (!drvdata->enable) {
ret = regulator_enable(drvdata->regulator);
if (ret) {
dev_err(&drvdata->client->dev,
"Enable regulator failed\n");
ret = lm3530_led_enable(drvdata);
if (ret)
return ret;
}
drvdata->enable = true;
}
for (i = 0; i < LM3530_REG_MAX; i++) {
/* do not update brightness register when pwm mode */
......@@ -305,13 +333,8 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev,
else
drvdata->brightness = brt_val;
if (brt_val == 0) {
err = regulator_disable(drvdata->regulator);
if (err)
dev_err(&drvdata->client->dev,
"Disable regulator failed\n");
drvdata->enable = false;
}
if (brt_val == 0)
lm3530_led_disable(drvdata);
break;
case LM3530_BL_MODE_ALS:
break;
......@@ -458,8 +481,7 @@ static int lm3530_remove(struct i2c_client *client)
device_remove_file(drvdata->led_dev.dev, &dev_attr_mode);
if (drvdata->enable)
regulator_disable(drvdata->regulator);
lm3530_led_disable(drvdata);
led_classdev_unregister(&drvdata->led_dev);
return 0;
}
......
......@@ -380,7 +380,7 @@ static void lm355x_indicator_brightness_set(struct led_classdev *cdev,
/* indicator pattern only for lm3556*/
static ssize_t lm3556_indicator_pattern_store(struct device *dev,
struct device_attribute *devAttr,
struct device_attribute *attr,
const char *buf, size_t size)
{
ssize_t ret;
......
......@@ -176,7 +176,7 @@ static int lm3642_control(struct lm3642_chip_data *chip,
/* torch pin config for lm3642*/
static ssize_t lm3642_torch_pin_store(struct device *dev,
struct device_attribute *devAttr,
struct device_attribute *attr,
const char *buf, size_t size)
{
ssize_t ret;
......@@ -233,7 +233,7 @@ static void lm3642_torch_brightness_set(struct led_classdev *cdev,
/* strobe pin config for lm3642*/
static ssize_t lm3642_strobe_pin_store(struct device *dev,
struct device_attribute *devAttr,
struct device_attribute *attr,
const char *buf, size_t size)
{
ssize_t ret;
......
......@@ -2,8 +2,10 @@
* LP5521 LED chip driver.
*
* Copyright (C) 2010 Nokia Corporation
* Copyright (C) 2012 Texas Instruments
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
* Milo(Woogyom) Kim <milo.kim@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
......@@ -20,33 +22,21 @@
* 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/ctype.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/leds-lp5521.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#define LP5521_PROGRAM_LENGTH 32 /* in bytes */
#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */
#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */
#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */
#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */
#include "leds-lp55xx-common.h"
#define LP5521_CMD_LOAD 0x15 /* 00010101 */
#define LP5521_CMD_RUN 0x2a /* 00101010 */
#define LP5521_CMD_DIRECT 0x3f /* 00111111 */
#define LP5521_CMD_DISABLED 0x00 /* 00000000 */
#define LP5521_PROGRAM_LENGTH 32
#define LP5521_MAX_LEDS 3
#define LP5521_CMD_DIRECT 0x3F
/* Registers */
#define LP5521_REG_ENABLE 0x00
......@@ -58,22 +48,14 @@
#define LP5521_REG_G_CURRENT 0x06
#define LP5521_REG_B_CURRENT 0x07
#define LP5521_REG_CONFIG 0x08
#define LP5521_REG_R_CHANNEL_PC 0x09
#define LP5521_REG_G_CHANNEL_PC 0x0A
#define LP5521_REG_B_CHANNEL_PC 0x0B
#define LP5521_REG_STATUS 0x0C
#define LP5521_REG_RESET 0x0D
#define LP5521_REG_GPO 0x0E
#define LP5521_REG_R_PROG_MEM 0x10
#define LP5521_REG_G_PROG_MEM 0x30
#define LP5521_REG_B_PROG_MEM 0x50
#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM
#define LP5521_PROG_MEM_SIZE 0x20
/* Base register to set LED current */
#define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT
/* Base register to set the brightness */
#define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM
......@@ -92,440 +74,287 @@
/* default R channel current register value */
#define LP5521_REG_R_CURR_DEFAULT 0xAF
/* Pattern Mode */
#define PATTERN_OFF 0
/* Reset register value */
#define LP5521_RESET 0xFF
struct lp5521_engine {
int id;
u8 mode;
u8 prog_page;
u8 engine_mask;
};
/* Program Memory Operations */
#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */
#define LP5521_MODE_G_M 0x0C
#define LP5521_MODE_B_M 0x03
#define LP5521_LOAD_R 0x10
#define LP5521_LOAD_G 0x04
#define LP5521_LOAD_B 0x01
struct lp5521_led {
int id;
u8 chan_nr;
u8 led_current;
u8 max_current;
struct led_classdev cdev;
struct work_struct brightness_work;
u8 brightness;
};
#define LP5521_R_IS_LOADING(mode) \
((mode & LP5521_MODE_R_M) == LP5521_LOAD_R)
#define LP5521_G_IS_LOADING(mode) \
((mode & LP5521_MODE_G_M) == LP5521_LOAD_G)
#define LP5521_B_IS_LOADING(mode) \
((mode & LP5521_MODE_B_M) == LP5521_LOAD_B)
struct lp5521_chip {
struct lp5521_platform_data *pdata;
struct mutex lock; /* Serialize control */
struct i2c_client *client;
struct lp5521_engine engines[LP5521_MAX_ENGINES];
struct lp5521_led leds[LP5521_MAX_LEDS];
u8 num_channels;
u8 num_leds;
};
#define LP5521_EXEC_R_M 0x30 /* Enable Register */
#define LP5521_EXEC_G_M 0x0C
#define LP5521_EXEC_B_M 0x03
#define LP5521_EXEC_M 0x3F
#define LP5521_RUN_R 0x20
#define LP5521_RUN_G 0x08
#define LP5521_RUN_B 0x02
static inline struct lp5521_led *cdev_to_led(struct led_classdev *cdev)
static inline void lp5521_wait_opmode_done(void)
{
return container_of(cdev, struct lp5521_led, cdev);
/* operation mode change needs to be longer than 153 us */
usleep_range(200, 300);
}
static inline struct lp5521_chip *engine_to_lp5521(struct lp5521_engine *engine)
static inline void lp5521_wait_enable_done(void)
{
return container_of(engine, struct lp5521_chip,
engines[engine->id - 1]);
/* it takes more 488 us to update ENABLE register */
usleep_range(500, 600);
}
static inline struct lp5521_chip *led_to_lp5521(struct lp5521_led *led)
static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current)
{
return container_of(led, struct lp5521_chip,
leds[led->id]);
led->led_current = led_current;
lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
led_current);
}
static void lp5521_led_brightness_work(struct work_struct *work);
static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value)
static void lp5521_load_engine(struct lp55xx_chip *chip)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
enum lp55xx_engine_index idx = chip->engine_idx;
u8 mask[] = {
[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
};
static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf)
{
s32 ret;
u8 val[] = {
[LP55XX_ENGINE_1] = LP5521_LOAD_R,
[LP55XX_ENGINE_2] = LP5521_LOAD_G,
[LP55XX_ENGINE_3] = LP5521_LOAD_B,
};
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
return ret;
lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]);
*buf = ret;
return 0;
lp5521_wait_opmode_done();
}
static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode)
static void lp5521_stop_engine(struct lp55xx_chip *chip)
{
struct lp5521_chip *chip = engine_to_lp5521(engine);
struct i2c_client *client = chip->client;
int ret;
u8 engine_state;
/* Only transition between RUN and DIRECT mode are handled here */
if (mode == LP5521_CMD_LOAD)
return 0;
if (mode == LP5521_CMD_DISABLED)
mode = LP5521_CMD_DIRECT;
ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state);
if (ret < 0)
return ret;
/* set mode only for this engine */
engine_state &= ~(engine->engine_mask);
mode &= engine->engine_mask;
engine_state |= mode;
return lp5521_write(client, LP5521_REG_OP_MODE, engine_state);
lp55xx_write(chip, LP5521_REG_OP_MODE, 0);
lp5521_wait_opmode_done();
}
static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern)
static void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
{
struct lp5521_chip *chip = engine_to_lp5521(eng);
struct i2c_client *client = chip->client;
int ret;
int addr;
u8 mode;
u8 exec;
/* move current engine to direct mode and remember the state */
ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT);
if (ret)
return ret;
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range(1000, 2000);
ret = lp5521_read(client, LP5521_REG_OP_MODE, &mode);
if (ret)
return ret;
/* For loading, all the engines to load mode */
lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range(1000, 2000);
lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD);
/* Mode change requires min 500 us delay. 1 - 2 ms with margin */
usleep_range(1000, 2000);
addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE;
i2c_smbus_write_i2c_block_data(client,
addr,
LP5521_PROG_MEM_SIZE,
pattern);
return lp5521_write(client, LP5521_REG_OP_MODE, mode);
}
static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr)
{
return lp5521_write(chip->client,
LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
curr);
}
static void lp5521_init_engine(struct lp5521_chip *chip)
{
int i;
for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
chip->engines[i].id = i + 1;
chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2);
chip->engines[i].prog_page = i;
/* stop engine */
if (!start) {
lp5521_stop_engine(chip);
lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
lp5521_wait_opmode_done();
return;
}
}
static int lp5521_configure(struct i2c_client *client)
{
struct lp5521_chip *chip = i2c_get_clientdata(client);
int ret;
u8 cfg;
lp5521_init_engine(chip);
/* Set all PWMs to direct control mode */
ret = lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
cfg = chip->pdata->update_config ?
: (LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT);
ret |= lp5521_write(client, LP5521_REG_CONFIG, cfg);
/* Initialize all channels PWM to zero -> leds off */
ret |= lp5521_write(client, LP5521_REG_R_PWM, 0);
ret |= lp5521_write(client, LP5521_REG_G_PWM, 0);
ret |= lp5521_write(client, LP5521_REG_B_PWM, 0);
/* Set engines are set to run state when OP_MODE enables engines */
ret |= lp5521_write(client, LP5521_REG_ENABLE,
LP5521_ENABLE_RUN_PROGRAM);
/* enable takes 500us. 1 - 2 ms leaves some margin */
usleep_range(1000, 2000);
return ret;
}
static int lp5521_run_selftest(struct lp5521_chip *chip, char *buf)
{
int ret;
u8 status;
ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status);
if (ret < 0)
return ret;
/* Check that ext clock is really in use if requested */
if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT)
if ((status & LP5521_EXT_CLK_USED) == 0)
return -EIO;
return 0;
}
static void lp5521_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp5521_led *led = cdev_to_led(cdev);
led->brightness = (u8)brightness;
schedule_work(&led->brightness_work);
}
static void lp5521_led_brightness_work(struct work_struct *work)
{
struct lp5521_led *led = container_of(work,
struct lp5521_led,
brightness_work);
struct lp5521_chip *chip = led_to_lp5521(led);
struct i2c_client *client = chip->client;
mutex_lock(&chip->lock);
lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
}
/* Detect the chip by setting its ENABLE register and reading it back. */
static int lp5521_detect(struct i2c_client *client)
{
int ret;
u8 buf;
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp5521_write(client, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT);
if (ret)
return ret;
/* enable takes 500us. 1 - 2 ms leaves some margin */
usleep_range(1000, 2000);
ret = lp5521_read(client, LP5521_REG_ENABLE, &buf);
ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode);
if (ret)
return ret;
if (buf != LP5521_ENABLE_DEFAULT)
return -ENODEV;
return;
return 0;
}
ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec);
if (ret)
return;
/* Set engine mode and create appropriate sysfs attributes, if required. */
static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode)
{
int ret = 0;
/* change operation mode to RUN only when each engine is loading */
if (LP5521_R_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R;
exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R;
}
/* if in that mode already do nothing, except for run */
if (mode == engine->mode && mode != LP5521_CMD_RUN)
return 0;
if (LP5521_G_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
}
if (mode == LP5521_CMD_RUN) {
ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN);
} else if (mode == LP5521_CMD_LOAD) {
lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
lp5521_set_engine_mode(engine, LP5521_CMD_LOAD);
} else if (mode == LP5521_CMD_DISABLED) {
lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
if (LP5521_B_IS_LOADING(mode)) {
mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B;
exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B;
}
engine->mode = mode;
lp55xx_write(chip, LP5521_REG_OP_MODE, mode);
lp5521_wait_opmode_done();
return ret;
lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec);
lp5521_wait_enable_done();
}
static int lp5521_do_store_load(struct lp5521_engine *engine,
const char *buf, size_t len)
static int lp5521_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
struct lp5521_chip *chip = engine_to_lp5521(engine);
struct i2c_client *client = chip->client;
int ret, nrchars, offset = 0, i = 0;
char c[3];
unsigned cmd;
enum lp55xx_engine_index idx = chip->engine_idx;
u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
u8 addr[] = {
[LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM,
[LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM,
[LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM,
};
unsigned cmd;
char c[3];
int program_size;
int nrchars;
int offset = 0;
int ret;
int i;
/* clear program memory before updating */
for (i = 0; i < LP5521_PROGRAM_LENGTH; i++)
lp55xx_write(chip, addr[idx] + i, 0);
while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) {
i = 0;
while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
if (ret != 2)
goto fail;
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto fail;
pattern[i] = (u8)cmd;
goto err;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto fail;
goto err;
mutex_lock(&chip->lock);
if (engine->mode == LP5521_CMD_LOAD)
ret = lp5521_load_program(engine, pattern);
else
ret = -EINVAL;
mutex_unlock(&chip->lock);
program_size = i;
for (i = 0; i < program_size; i++)
lp55xx_write(chip, addr[idx] + i, pattern[i]);
if (ret) {
dev_err(&client->dev, "failed loading pattern\n");
return ret;
}
return 0;
return len;
fail:
dev_err(&client->dev, "wrong pattern format\n");
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
static ssize_t store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
static void lp5521_firmware_loaded(struct lp55xx_chip *chip)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5521_chip *chip = i2c_get_clientdata(client);
return lp5521_do_store_load(&chip->engines[nr - 1], buf, len);
}
const struct firmware *fw = chip->fw;
#define store_load(nr) \
static ssize_t store_engine##nr##_load(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_load(dev, attr, buf, len, nr); \
}
store_load(1)
store_load(2)
store_load(3)
static ssize_t show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5521_chip *chip = i2c_get_clientdata(client);
switch (chip->engines[nr - 1].mode) {
case LP5521_CMD_RUN:
return sprintf(buf, "run\n");
case LP5521_CMD_LOAD:
return sprintf(buf, "load\n");
case LP5521_CMD_DISABLED:
return sprintf(buf, "disabled\n");
default:
return sprintf(buf, "disabled\n");
if (fw->size > LP5521_PROGRAM_LENGTH) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
}
#define show_mode(nr) \
static ssize_t show_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return show_engine_mode(dev, attr, buf, nr); \
/*
* Program momery sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
lp5521_load_engine(chip);
lp5521_update_program_memory(chip, fw->data, fw->size);
}
show_mode(1)
show_mode(2)
show_mode(3)
static ssize_t store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
static int lp5521_post_init_device(struct lp55xx_chip *chip)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5521_chip *chip = i2c_get_clientdata(client);
struct lp5521_engine *engine = &chip->engines[nr - 1];
mutex_lock(&chip->lock);
int ret;
u8 val;
if (!strncmp(buf, "run", 3))
lp5521_set_mode(engine, LP5521_CMD_RUN);
else if (!strncmp(buf, "load", 4))
lp5521_set_mode(engine, LP5521_CMD_LOAD);
else if (!strncmp(buf, "disabled", 8))
lp5521_set_mode(engine, LP5521_CMD_DISABLED);
/*
* Make sure that the chip is reset by reading back the r channel
* current reg. This is dummy read is required on some platforms -
* otherwise further access to the R G B channels in the
* LP5521_REG_ENABLE register will not have any effect - strange!
*/
ret = lp55xx_read(chip, LP5521_REG_R_CURRENT, &val);
if (ret) {
dev_err(&chip->cl->dev, "error in resetting chip\n");
return ret;
}
if (val != LP5521_REG_R_CURR_DEFAULT) {
dev_err(&chip->cl->dev,
"unexpected data in register (expected 0x%x got 0x%x)\n",
LP5521_REG_R_CURR_DEFAULT, val);
ret = -EINVAL;
return ret;
}
usleep_range(10000, 20000);
mutex_unlock(&chip->lock);
return len;
}
/* Set all PWMs to direct control mode */
ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
#define store_mode(nr) \
static ssize_t store_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_mode(dev, attr, buf, len, nr); \
}
store_mode(1)
store_mode(2)
store_mode(3)
val = chip->pdata->update_config ?
: (LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT);
ret = lp55xx_write(chip, LP5521_REG_CONFIG, val);
if (ret)
return ret;
static ssize_t show_max_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5521_led *led = cdev_to_led(led_cdev);
/* Initialize all channels PWM to zero -> leds off */
lp55xx_write(chip, LP5521_REG_R_PWM, 0);
lp55xx_write(chip, LP5521_REG_G_PWM, 0);
lp55xx_write(chip, LP5521_REG_B_PWM, 0);
return sprintf(buf, "%d\n", led->max_current);
}
/* Set engines are set to run state when OP_MODE enables engines */
ret = lp55xx_write(chip, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM);
if (ret)
return ret;
static ssize_t show_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5521_led *led = cdev_to_led(led_cdev);
lp5521_wait_enable_done();
return sprintf(buf, "%d\n", led->led_current);
return 0;
}
static ssize_t store_current(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5521_led *led = cdev_to_led(led_cdev);
struct lp5521_chip *chip = led_to_lp5521(led);
ssize_t ret;
unsigned long curr;
struct lp55xx_platform_data *pdata = chip->pdata;
int ret;
u8 status;
if (kstrtoul(buf, 0, &curr))
return -EINVAL;
ret = lp55xx_read(chip, LP5521_REG_STATUS, &status);
if (ret < 0)
return ret;
if (curr > led->max_current)
return -EINVAL;
if (pdata->clock_mode != LP55XX_CLOCK_EXT)
return 0;
mutex_lock(&chip->lock);
ret = lp5521_set_led_current(chip, led->id, curr);
mutex_unlock(&chip->lock);
/* Check that ext clock is really in use if requested */
if ((status & LP5521_EXT_CLK_USED) == 0)
return -EIO;
if (ret < 0)
return ret;
return 0;
}
led->led_current = (u8)curr;
static void lp5521_led_brightness_work(struct work_struct *work)
{
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
brightness_work);
struct lp55xx_chip *chip = led->chip;
return len;
mutex_lock(&chip->lock);
lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
}
static ssize_t lp5521_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5521_chip *chip = i2c_get_clientdata(client);
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
......@@ -534,133 +363,11 @@ static ssize_t lp5521_selftest(struct device *dev,
return sprintf(buf, "%s\n", ret ? "FAIL" : "OK");
}
static void lp5521_clear_program_memory(struct i2c_client *cl)
{
int i;
u8 rgb_mem[] = {
LP5521_REG_R_PROG_MEM,
LP5521_REG_G_PROG_MEM,
LP5521_REG_B_PROG_MEM,
};
for (i = 0; i < ARRAY_SIZE(rgb_mem); i++) {
lp5521_write(cl, rgb_mem[i], 0);
lp5521_write(cl, rgb_mem[i] + 1, 0);
}
}
static void lp5521_write_program_memory(struct i2c_client *cl,
u8 base, u8 *rgb, int size)
{
int i;
if (!rgb || size <= 0)
return;
for (i = 0; i < size; i++)
lp5521_write(cl, base + i, *(rgb + i));
lp5521_write(cl, base + i, 0);
lp5521_write(cl, base + i + 1, 0);
}
static inline struct lp5521_led_pattern *lp5521_get_pattern
(struct lp5521_chip *chip, u8 offset)
{
struct lp5521_led_pattern *ptn;
ptn = chip->pdata->patterns + (offset - 1);
return ptn;
}
static void lp5521_run_led_pattern(int mode, struct lp5521_chip *chip)
{
struct lp5521_led_pattern *ptn;
struct i2c_client *cl = chip->client;
int num_patterns = chip->pdata->num_patterns;
if (mode > num_patterns || !(chip->pdata->patterns))
return;
if (mode == PATTERN_OFF) {
lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT);
usleep_range(1000, 2000);
lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
} else {
ptn = lp5521_get_pattern(chip, mode);
if (!ptn)
return;
lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_LOAD);
usleep_range(1000, 2000);
lp5521_clear_program_memory(cl);
lp5521_write_program_memory(cl, LP5521_REG_R_PROG_MEM,
ptn->r, ptn->size_r);
lp5521_write_program_memory(cl, LP5521_REG_G_PROG_MEM,
ptn->g, ptn->size_g);
lp5521_write_program_memory(cl, LP5521_REG_B_PROG_MEM,
ptn->b, ptn->size_b);
lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_RUN);
usleep_range(1000, 2000);
lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM);
}
}
static ssize_t store_led_pattern(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp5521_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
unsigned long val;
int ret;
ret = kstrtoul(buf, 16, &val);
if (ret)
return ret;
lp5521_run_led_pattern(val, chip);
return len;
}
/* led class device attributes */
static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current);
static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
static struct attribute *lp5521_led_attributes[] = {
&dev_attr_led_current.attr,
&dev_attr_max_current.attr,
NULL,
};
static struct attribute_group lp5521_led_attribute_group = {
.attrs = lp5521_led_attributes
};
/* device attributes */
static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUSR,
show_engine1_mode, store_engine1_mode);
static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUSR,
show_engine2_mode, store_engine2_mode);
static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUSR,
show_engine3_mode, store_engine3_mode);
static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load);
static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load);
static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load);
static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL);
static DEVICE_ATTR(led_pattern, S_IWUSR, NULL, store_led_pattern);
static struct attribute *lp5521_attributes[] = {
&dev_attr_engine1_mode.attr,
&dev_attr_engine2_mode.attr,
&dev_attr_engine3_mode.attr,
&dev_attr_selftest.attr,
&dev_attr_engine1_load.attr,
&dev_attr_engine2_load.attr,
&dev_attr_engine3_load.attr,
&dev_attr_led_pattern.attr,
NULL
};
......@@ -668,217 +375,91 @@ static const struct attribute_group lp5521_group = {
.attrs = lp5521_attributes,
};
static int lp5521_register_sysfs(struct i2c_client *client)
{
struct device *dev = &client->dev;
return sysfs_create_group(&dev->kobj, &lp5521_group);
}
static void lp5521_unregister_sysfs(struct i2c_client *client)
{
struct lp5521_chip *chip = i2c_get_clientdata(client);
struct device *dev = &client->dev;
int i;
sysfs_remove_group(&dev->kobj, &lp5521_group);
for (i = 0; i < chip->num_leds; i++)
sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
&lp5521_led_attribute_group);
}
static int lp5521_init_led(struct lp5521_led *led,
struct i2c_client *client,
int chan, struct lp5521_platform_data *pdata)
{
struct device *dev = &client->dev;
char name[32];
int res;
if (chan >= LP5521_MAX_LEDS)
return -EINVAL;
if (pdata->led_config[chan].led_current == 0)
return 0;
led->led_current = pdata->led_config[chan].led_current;
led->max_current = pdata->led_config[chan].max_current;
led->chan_nr = pdata->led_config[chan].chan_nr;
if (led->chan_nr >= LP5521_MAX_LEDS) {
dev_err(dev, "Use channel numbers between 0 and %d\n",
LP5521_MAX_LEDS - 1);
return -EINVAL;
}
led->cdev.brightness_set = lp5521_set_brightness;
if (pdata->led_config[chan].name) {
led->cdev.name = pdata->led_config[chan].name;
} else {
snprintf(name, sizeof(name), "%s:channel%d",
pdata->label ?: client->name, chan);
led->cdev.name = name;
}
res = led_classdev_register(dev, &led->cdev);
if (res < 0) {
dev_err(dev, "couldn't register led on channel %d\n", chan);
return res;
}
res = sysfs_create_group(&led->cdev.dev->kobj,
&lp5521_led_attribute_group);
if (res < 0) {
dev_err(dev, "couldn't register current attribute\n");
led_classdev_unregister(&led->cdev);
return res;
}
return 0;
}
/* Chip specific configurations */
static struct lp55xx_device_config lp5521_cfg = {
.reset = {
.addr = LP5521_REG_RESET,
.val = LP5521_RESET,
},
.enable = {
.addr = LP5521_REG_ENABLE,
.val = LP5521_ENABLE_DEFAULT,
},
.max_channel = LP5521_MAX_LEDS,
.post_init_device = lp5521_post_init_device,
.brightness_work_fn = lp5521_led_brightness_work,
.set_led_current = lp5521_set_led_current,
.firmware_cb = lp5521_firmware_loaded,
.run_engine = lp5521_run_engine,
.dev_attr_group = &lp5521_group,
};
static int lp5521_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lp5521_chip *chip;
struct lp5521_platform_data *pdata;
int ret, i, led;
u8 buf;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
i2c_set_clientdata(client, chip);
chip->client = client;
pdata = client->dev.platform_data;
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
mutex_init(&chip->lock);
chip->pdata = pdata;
if (pdata->setup_resources) {
ret = pdata->setup_resources();
if (ret < 0)
return ret;
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
if (pdata->enable) {
pdata->enable(0);
usleep_range(1000, 2000); /* Keep enable down at least 1ms */
pdata->enable(1);
usleep_range(1000, 2000); /* 500us abs min. */
}
led = devm_kzalloc(&client->dev,
sizeof(*led) * pdata->num_channels, GFP_KERNEL);
if (!led)
return -ENOMEM;
lp5521_write(client, LP5521_REG_RESET, 0xff);
usleep_range(10000, 20000); /*
* Exact value is not available. 10 - 20ms
* appears to be enough for reset.
*/
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp5521_cfg;
/*
* Make sure that the chip is reset by reading back the r channel
* current reg. This is dummy read is required on some platforms -
* otherwise further access to the R G B channels in the
* LP5521_REG_ENABLE register will not have any effect - strange!
*/
ret = lp5521_read(client, LP5521_REG_R_CURRENT, &buf);
if (ret) {
dev_err(&client->dev, "error in resetting chip\n");
goto fail2;
}
if (buf != LP5521_REG_R_CURR_DEFAULT) {
dev_err(&client->dev,
"unexpected data in register (expected 0x%x got 0x%x)\n",
LP5521_REG_R_CURR_DEFAULT, buf);
ret = -EINVAL;
goto fail2;
}
usleep_range(10000, 20000);
mutex_init(&chip->lock);
ret = lp5521_detect(client);
i2c_set_clientdata(client, led);
if (ret) {
dev_err(&client->dev, "Chip not found\n");
goto fail2;
}
ret = lp55xx_init_device(chip);
if (ret)
goto err_init;
dev_info(&client->dev, "%s programmable led chip found\n", id->name);
ret = lp5521_configure(client);
if (ret < 0) {
dev_err(&client->dev, "error configuring chip\n");
goto fail1;
}
/* Initialize leds */
chip->num_channels = pdata->num_channels;
chip->num_leds = 0;
led = 0;
for (i = 0; i < pdata->num_channels; i++) {
/* Do not initialize channels that are not connected */
if (pdata->led_config[i].led_current == 0)
continue;
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
ret = lp5521_init_led(&chip->leds[led], client, i, pdata);
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "error initializing leds\n");
goto fail2;
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
}
chip->num_leds++;
chip->leds[led].id = led;
/* Set initial LED current */
lp5521_set_led_current(chip, led,
chip->leds[led].led_current);
INIT_WORK(&(chip->leds[led].brightness_work),
lp5521_led_brightness_work);
led++;
}
return 0;
ret = lp5521_register_sysfs(client);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto fail2;
}
return ret;
fail2:
for (i = 0; i < chip->num_leds; i++) {
led_classdev_unregister(&chip->leds[i].cdev);
cancel_work_sync(&chip->leds[i].brightness_work);
}
fail1:
if (pdata->enable)
pdata->enable(0);
if (pdata->release_resources)
pdata->release_resources();
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static int lp5521_remove(struct i2c_client *client)
{
struct lp5521_chip *chip = i2c_get_clientdata(client);
int i;
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp5521_run_led_pattern(PATTERN_OFF, chip);
lp5521_unregister_sysfs(client);
for (i = 0; i < chip->num_leds; i++) {
led_classdev_unregister(&chip->leds[i].cdev);
cancel_work_sync(&chip->leds[i].brightness_work);
}
lp5521_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
if (chip->pdata->enable)
chip->pdata->enable(0);
if (chip->pdata->release_resources)
chip->pdata->release_resources();
return 0;
}
......@@ -900,5 +481,6 @@ static struct i2c_driver lp5521_driver = {
module_i2c_driver(lp5521_driver);
MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo");
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP5521 LED engine");
MODULE_LICENSE("GPL v2");
......@@ -2,8 +2,10 @@
* lp5523.c - LP5523 LED Driver
*
* Copyright (C) 2010 Nokia Corporation
* Copyright (C) 2012 Texas Instruments
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
* Milo(Woogyom) Kim <milo.kim@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
......@@ -20,502 +22,351 @@
* 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/ctype.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/leds-lp5523.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#include "leds-lp55xx-common.h"
#define LP5523_PROGRAM_LENGTH 32
#define LP5523_MAX_LEDS 9
/* Registers */
#define LP5523_REG_ENABLE 0x00
#define LP5523_REG_OP_MODE 0x01
#define LP5523_REG_RATIOMETRIC_MSB 0x02
#define LP5523_REG_RATIOMETRIC_LSB 0x03
#define LP5523_REG_ENABLE_LEDS_MSB 0x04
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
#define LP5523_REG_LED_CNTRL_BASE 0x06
#define LP5523_REG_LED_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36
#define LP5523_REG_CHANNEL1_PC 0x37
#define LP5523_REG_CHANNEL2_PC 0x38
#define LP5523_REG_CHANNEL3_PC 0x39
#define LP5523_REG_STATUS 0x3a
#define LP5523_REG_GPO 0x3b
#define LP5523_REG_VARIABLE 0x3c
#define LP5523_REG_RESET 0x3d
#define LP5523_REG_TEMP_CTRL 0x3e
#define LP5523_REG_TEMP_READ 0x3f
#define LP5523_REG_TEMP_WRITE 0x40
#define LP5523_REG_STATUS 0x3A
#define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42
#define LP5523_REG_ENG1_VARIABLE 0x45
#define LP5523_REG_ENG2_VARIABLE 0x46
#define LP5523_REG_ENG3_VARIABLE 0x47
#define LP5523_REG_MASTER_FADER1 0x48
#define LP5523_REG_MASTER_FADER2 0x49
#define LP5523_REG_MASTER_FADER3 0x4a
#define LP5523_REG_CH1_PROG_START 0x4c
#define LP5523_REG_CH2_PROG_START 0x4d
#define LP5523_REG_CH3_PROG_START 0x4e
#define LP5523_REG_PROG_PAGE_SEL 0x4f
#define LP5523_REG_PROG_PAGE_SEL 0x4F
#define LP5523_REG_PROG_MEM 0x50
#define LP5523_CMD_LOAD 0x15 /* 00010101 */
#define LP5523_CMD_RUN 0x2a /* 00101010 */
#define LP5523_CMD_DISABLED 0x00 /* 00000000 */
/* Bit description in registers */
#define LP5523_ENABLE 0x40
#define LP5523_AUTO_INC 0x40
#define LP5523_PWR_SAVE 0x20
#define LP5523_PWM_PWR_SAVE 0x04
#define LP5523_CP_1 0x08
#define LP5523_CP_1_5 0x10
#define LP5523_CP_AUTO 0x18
#define LP5523_INT_CLK 0x01
#define LP5523_AUTO_CLK 0x02
#define LP5523_EN_LEDTEST 0x80
#define LP5523_LEDTEST_DONE 0x80
#define LP5523_DEFAULT_CURRENT 50 /* microAmps */
#define LP5523_PROGRAM_LENGTH 32 /* in bytes */
#define LP5523_PROGRAM_PAGES 6
#define LP5523_RESET 0xFF
#define LP5523_ADC_SHORTCIRC_LIM 80
#define LP5523_LEDS 9
#define LP5523_ENGINES 3
#define LP5523_ENG_MASK_BASE 0x30 /* 00110000 */
#define LP5523_ENG_STATUS_MASK 0x07 /* 00000111 */
#define LP5523_IRQ_FLAGS IRQF_TRIGGER_FALLING
#define LP5523_EXT_CLK_USED 0x08
#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
#define SHIFT_MASK(id) (((id) - 1) * 2)
/* Memory Page Selection */
#define LP5523_PAGE_ENG1 0
#define LP5523_PAGE_ENG2 1
#define LP5523_PAGE_ENG3 2
/* Program Memory Operations */
#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */
#define LP5523_MODE_ENG2_M 0x0C
#define LP5523_MODE_ENG3_M 0x03
#define LP5523_LOAD_ENG1 0x10
#define LP5523_LOAD_ENG2 0x04
#define LP5523_LOAD_ENG3 0x01
#define LP5523_ENG1_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1)
#define LP5523_ENG2_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2)
#define LP5523_ENG3_IS_LOADING(mode) \
((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3)
#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */
#define LP5523_EXEC_ENG2_M 0x0C
#define LP5523_EXEC_ENG3_M 0x03
#define LP5523_EXEC_M 0x3F
#define LP5523_RUN_ENG1 0x20
#define LP5523_RUN_ENG2 0x08
#define LP5523_RUN_ENG3 0x02
enum lp5523_chip_id {
LP5523,
LP55231,
};
struct lp5523_engine {
int id;
u8 mode;
u8 prog_page;
u8 mux_page;
u16 led_mux;
u8 engine_mask;
};
struct lp5523_led {
int id;
u8 chan_nr;
u8 led_current;
u8 max_current;
struct led_classdev cdev;
struct work_struct brightness_work;
u8 brightness;
};
struct lp5523_chip {
struct mutex lock; /* Serialize control */
struct i2c_client *client;
struct lp5523_engine engines[LP5523_ENGINES];
struct lp5523_led leds[LP5523_LEDS];
struct lp5523_platform_data *pdata;
u8 num_channels;
u8 num_leds;
};
static inline struct lp5523_led *cdev_to_led(struct led_classdev *cdev)
{
return container_of(cdev, struct lp5523_led, cdev);
}
static inline struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine)
{
return container_of(engine, struct lp5523_chip,
engines[engine->id - 1]);
}
static inline struct lp5523_chip *led_to_lp5523(struct lp5523_led *led)
static inline void lp5523_wait_opmode_done(void)
{
return container_of(led, struct lp5523_chip,
leds[led->id]);
}
static void lp5523_set_mode(struct lp5523_engine *engine, u8 mode);
static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode);
static int lp5523_load_program(struct lp5523_engine *engine, const u8 *pattern);
static void lp5523_led_brightness_work(struct work_struct *work);
static int lp5523_write(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
usleep_range(1000, 2000);
}
static int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf)
static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current)
{
s32 ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
return ret;
*buf = ret;
return 0;
led->led_current = led_current;
lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
led_current);
}
static int lp5523_detect(struct i2c_client *client)
static int lp5523_post_init_device(struct lp55xx_chip *chip)
{
int ret;
u8 buf;
ret = lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE);
if (ret)
return ret;
ret = lp5523_read(client, LP5523_REG_ENABLE, &buf);
ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE);
if (ret)
return ret;
if (buf == 0x40)
return 0;
else
return -ENODEV;
}
static int lp5523_configure(struct i2c_client *client)
{
struct lp5523_chip *chip = i2c_get_clientdata(client);
int ret = 0;
u8 status;
/* one pattern per engine setting led mux start and stop addresses */
static const u8 pattern[][LP5523_PROGRAM_LENGTH] = {
{ 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
};
ret |= lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE);
/* Chip startup time is 500 us, 1 - 2 ms gives some margin */
usleep_range(1000, 2000);
ret |= lp5523_write(client, LP5523_REG_CONFIG,
ret = lp55xx_write(chip, LP5523_REG_CONFIG,
LP5523_AUTO_INC | LP5523_PWR_SAVE |
LP5523_CP_AUTO | LP5523_AUTO_CLK |
LP5523_PWM_PWR_SAVE);
if (ret)
return ret;
/* turn on all leds */
ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
/* hardcode 32 bytes of memory for each engine from program memory */
ret |= lp5523_write(client, LP5523_REG_CH1_PROG_START, 0x00);
ret |= lp5523_write(client, LP5523_REG_CH2_PROG_START, 0x10);
ret |= lp5523_write(client, LP5523_REG_CH3_PROG_START, 0x20);
/* write led mux address space for each channel */
ret |= lp5523_load_program(&chip->engines[0], pattern[0]);
ret |= lp5523_load_program(&chip->engines[1], pattern[1]);
ret |= lp5523_load_program(&chip->engines[2], pattern[2]);
if (ret) {
dev_err(&client->dev, "could not load mux programs\n");
return -1;
}
/* set all engines exec state and mode to run 00101010 */
ret |= lp5523_write(client, LP5523_REG_ENABLE,
(LP5523_CMD_RUN | LP5523_ENABLE));
ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_RUN);
if (ret) {
dev_err(&client->dev, "could not start mux programs\n");
return -1;
}
/* Let the programs run for couple of ms and check the engine status */
usleep_range(3000, 6000);
ret = lp5523_read(client, LP5523_REG_STATUS, &status);
if (ret < 0)
ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
if (ret)
return ret;
status &= LP5523_ENG_STATUS_MASK;
if (status == LP5523_ENG_STATUS_MASK) {
dev_dbg(&client->dev, "all engines configured\n");
} else {
dev_info(&client->dev, "status == %x\n", status);
dev_err(&client->dev, "cound not configure LED engine\n");
return -1;
}
dev_info(&client->dev, "disabling engines\n");
ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED);
return ret;
return lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
}
static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode)
static void lp5523_load_engine(struct lp55xx_chip *chip)
{
struct lp5523_chip *chip = engine_to_lp5523(engine);
struct i2c_client *client = chip->client;
int ret;
u8 engine_state;
enum lp55xx_engine_index idx = chip->engine_idx;
u8 mask[] = {
[LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
[LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
[LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
};
ret = lp5523_read(client, LP5523_REG_OP_MODE, &engine_state);
if (ret)
goto fail;
u8 val[] = {
[LP55XX_ENGINE_1] = LP5523_LOAD_ENG1,
[LP55XX_ENGINE_2] = LP5523_LOAD_ENG2,
[LP55XX_ENGINE_3] = LP5523_LOAD_ENG3,
};
engine_state &= ~(engine->engine_mask);
u8 page_sel[] = {
[LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
[LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
[LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
};
/* set mode only for this engine */
mode &= engine->engine_mask;
lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
engine_state |= mode;
lp5523_wait_opmode_done();
ret |= lp5523_write(client, LP5523_REG_OP_MODE, engine_state);
fail:
return ret;
lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
}
static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux)
static void lp5523_stop_engine(struct lp55xx_chip *chip)
{
struct lp5523_chip *chip = engine_to_lp5523(engine);
struct i2c_client *client = chip->client;
int ret = 0;
ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, engine->mux_page);
ret |= lp5523_write(client, LP5523_REG_PROG_MEM,
(u8)(mux >> 8));
ret |= lp5523_write(client, LP5523_REG_PROG_MEM + 1, (u8)(mux));
engine->led_mux = mux;
return ret;
lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
lp5523_wait_opmode_done();
}
static int lp5523_load_program(struct lp5523_engine *engine, const u8 *pattern)
static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
{
struct lp5523_chip *chip = engine_to_lp5523(engine);
struct i2c_client *client = chip->client;
int ret = 0;
ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL,
engine->prog_page);
ret |= i2c_smbus_write_i2c_block_data(client, LP5523_REG_PROG_MEM,
LP5523_PROGRAM_LENGTH, pattern);
int i;
return ret;
for (i = 0; i < LP5523_MAX_LEDS; i++)
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
}
static int lp5523_run_program(struct lp5523_engine *engine)
static void lp5523_run_engine(struct lp55xx_chip *chip, bool start)
{
struct lp5523_chip *chip = engine_to_lp5523(engine);
struct i2c_client *client = chip->client;
int ret;
u8 mode;
u8 exec;
ret = lp5523_write(client, LP5523_REG_ENABLE,
LP5523_CMD_RUN | LP5523_ENABLE);
/* stop engine */
if (!start) {
lp5523_stop_engine(chip);
lp5523_turn_off_channels(chip);
return;
}
/*
* To run the engine,
* operation mode and enable register should updated at the same time
*/
ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode);
if (ret)
goto fail;
return;
ret = lp5523_set_engine_mode(engine, LP5523_CMD_RUN);
fail:
return ret;
}
ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec);
if (ret)
return;
static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
{
int i;
u16 tmp_mux = 0;
len = min_t(int, len, LP5523_LEDS);
for (i = 0; i < len; i++) {
switch (buf[i]) {
case '1':
tmp_mux |= (1 << i);
break;
case '0':
break;
case '\n':
i = len;
break;
default:
return -1;
/* change operation mode to RUN only when each engine is loading */
if (LP5523_ENG1_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1;
exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1;
}
if (LP5523_ENG2_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2;
exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2;
}
*mux = tmp_mux;
return 0;
if (LP5523_ENG3_IS_LOADING(mode)) {
mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3;
exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3;
}
lp55xx_write(chip, LP5523_REG_OP_MODE, mode);
lp5523_wait_opmode_done();
lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec);
}
static void lp5523_mux_to_array(u16 led_mux, char *array)
static int lp5523_update_program_memory(struct lp55xx_chip *chip,
const u8 *data, size_t size)
{
int i, pos = 0;
for (i = 0; i < LP5523_LEDS; i++)
pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
unsigned cmd;
char c[3];
int update_size;
int nrchars;
int offset = 0;
int ret;
int i;
array[pos] = '\0';
}
/* clear program memory before updating */
for (i = 0; i < LP5523_PROGRAM_LENGTH; i++)
lp55xx_write(chip, LP5523_REG_PROG_MEM + i, 0);
/*--------------------------------------------------------------*/
/* Sysfs interface */
/*--------------------------------------------------------------*/
i = 0;
while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
if (ret != 1)
goto err;
static ssize_t show_engine_leds(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
char mux[LP5523_LEDS + 1];
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto err;
lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
return sprintf(buf, "%s\n", mux);
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto err;
#define show_leds(nr) \
static ssize_t show_engine##nr##_leds(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return show_engine_leds(dev, attr, buf, nr); \
}
show_leds(1)
show_leds(2)
show_leds(3)
update_size = i;
for (i = 0; i < update_size; i++)
lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
static ssize_t store_engine_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
u16 mux = 0;
ssize_t ret;
return 0;
if (lp5523_mux_parse(buf, &mux, len))
err:
dev_err(&chip->cl->dev, "wrong pattern format\n");
return -EINVAL;
}
mutex_lock(&chip->lock);
ret = -EINVAL;
if (chip->engines[nr - 1].mode != LP5523_CMD_LOAD)
goto leave;
static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
{
const struct firmware *fw = chip->fw;
if (lp5523_load_mux(&chip->engines[nr - 1], mux))
goto leave;
if (fw->size > LP5523_PROGRAM_LENGTH) {
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
fw->size);
return;
}
ret = len;
leave:
mutex_unlock(&chip->lock);
return ret;
}
/*
* Program momery sequence
* 1) set engine mode to "LOAD"
* 2) write firmware data into program memory
*/
#define store_leds(nr) \
static ssize_t store_engine##nr##_leds(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_leds(dev, attr, buf, len, nr); \
lp5523_load_engine(chip);
lp5523_update_program_memory(chip, fw->data, fw->size);
}
store_leds(1)
store_leds(2)
store_leds(3)
static ssize_t lp5523_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_platform_data *pdata = chip->pdata;
int i, ret, pos = 0;
int led = 0;
u8 status, adc, vdd;
mutex_lock(&chip->lock);
ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
/* Check that ext clock is really in use if requested */
if ((chip->pdata) && (chip->pdata->clock_mode == LP5523_CLOCK_EXT))
if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
if ((status & LP5523_EXT_CLK_USED) == 0)
goto fail;
}
/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
lp5523_write(chip->client, LP5523_REG_LED_TEST_CTRL,
LP5523_EN_LEDTEST | 16);
lp55xx_write(chip, LP5523_REG_LED_TEST_CTRL, LP5523_EN_LEDTEST | 16);
usleep_range(3000, 6000); /* ADC conversion time is typically 2.7 ms */
ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000); /* Was not ready. Wait little bit */
ret = lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &vdd);
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &vdd);
if (ret < 0)
goto fail;
vdd--; /* There may be some fluctuation in measurement */
for (i = 0; i < LP5523_LEDS; i++) {
for (i = 0; i < LP5523_MAX_LEDS; i++) {
/* Skip non-existing channels */
if (chip->pdata->led_config[i].led_current == 0)
if (pdata->led_config[i].led_current == 0)
continue;
/* Set default current */
lp5523_write(chip->client,
LP5523_REG_LED_CURRENT_BASE + i,
chip->pdata->led_config[i].led_current);
lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
pdata->led_config[i].led_current);
lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0xff);
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0xff);
/* let current stabilize 2 - 4ms before measurements start */
usleep_range(2000, 4000);
lp5523_write(chip->client,
LP5523_REG_LED_TEST_CTRL,
lp55xx_write(chip, LP5523_REG_LED_TEST_CTRL,
LP5523_EN_LEDTEST | i);
/* ADC conversion time is 2.7 ms typically */
usleep_range(3000, 6000);
ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000);/* Was not ready. Wait. */
ret = lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &adc);
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc);
if (ret < 0)
goto fail;
if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM)
pos += sprintf(buf + pos, "LED %d FAIL\n", i);
lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0x00);
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0x00);
/* Restore current */
lp5523_write(chip->client,
LP5523_REG_LED_CURRENT_BASE + i,
chip->leds[led].led_current);
lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
led->led_current);
led++;
}
if (pos == 0)
......@@ -530,249 +381,22 @@ static ssize_t lp5523_selftest(struct device *dev,
return pos;
}
static void lp5523_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp5523_led *led = cdev_to_led(cdev);
led->brightness = (u8)brightness;
schedule_work(&led->brightness_work);
}
static void lp5523_led_brightness_work(struct work_struct *work)
{
struct lp5523_led *led = container_of(work,
struct lp5523_led,
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
brightness_work);
struct lp5523_chip *chip = led_to_lp5523(led);
struct i2c_client *client = chip->client;
struct lp55xx_chip *chip = led->chip;
mutex_lock(&chip->lock);
lp5523_write(client, LP5523_REG_LED_PWM_BASE + led->chan_nr,
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
mutex_unlock(&chip->lock);
}
static int lp5523_do_store_load(struct lp5523_engine *engine,
const char *buf, size_t len)
{
struct lp5523_chip *chip = engine_to_lp5523(engine);
struct i2c_client *client = chip->client;
int ret, nrchars, offset = 0, i = 0;
char c[3];
unsigned cmd;
u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
if (engine->mode != LP5523_CMD_LOAD)
return -EINVAL;
while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) {
/* separate sscanfs because length is working only for %s */
ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
ret = sscanf(c, "%2x", &cmd);
if (ret != 1)
goto fail;
pattern[i] = (u8)cmd;
offset += nrchars;
i++;
}
/* Each instruction is 16bit long. Check that length is even */
if (i % 2)
goto fail;
mutex_lock(&chip->lock);
ret = lp5523_load_program(engine, pattern);
mutex_unlock(&chip->lock);
if (ret) {
dev_err(&client->dev, "failed loading pattern\n");
return ret;
}
return len;
fail:
dev_err(&client->dev, "wrong pattern format\n");
return -EINVAL;
}
static ssize_t store_engine_load(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
return lp5523_do_store_load(&chip->engines[nr - 1], buf, len);
}
#define store_load(nr) \
static ssize_t store_engine##nr##_load(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_load(dev, attr, buf, len, nr); \
}
store_load(1)
store_load(2)
store_load(3)
static ssize_t show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
switch (chip->engines[nr - 1].mode) {
case LP5523_CMD_RUN:
return sprintf(buf, "run\n");
case LP5523_CMD_LOAD:
return sprintf(buf, "load\n");
case LP5523_CMD_DISABLED:
return sprintf(buf, "disabled\n");
default:
return sprintf(buf, "disabled\n");
}
}
#define show_mode(nr) \
static ssize_t show_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
return show_engine_mode(dev, attr, buf, nr); \
}
show_mode(1)
show_mode(2)
show_mode(3)
static ssize_t store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5523_chip *chip = i2c_get_clientdata(client);
struct lp5523_engine *engine = &chip->engines[nr - 1];
mutex_lock(&chip->lock);
if (!strncmp(buf, "run", 3))
lp5523_set_mode(engine, LP5523_CMD_RUN);
else if (!strncmp(buf, "load", 4))
lp5523_set_mode(engine, LP5523_CMD_LOAD);
else if (!strncmp(buf, "disabled", 8))
lp5523_set_mode(engine, LP5523_CMD_DISABLED);
mutex_unlock(&chip->lock);
return len;
}
#define store_mode(nr) \
static ssize_t store_engine##nr##_mode(struct device *dev, \
struct device_attribute *attr, \
const char *buf, size_t len) \
{ \
return store_engine_mode(dev, attr, buf, len, nr); \
}
store_mode(1)
store_mode(2)
store_mode(3)
static ssize_t show_max_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5523_led *led = cdev_to_led(led_cdev);
return sprintf(buf, "%d\n", led->max_current);
}
static ssize_t show_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5523_led *led = cdev_to_led(led_cdev);
return sprintf(buf, "%d\n", led->led_current);
}
static ssize_t store_current(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lp5523_led *led = cdev_to_led(led_cdev);
struct lp5523_chip *chip = led_to_lp5523(led);
ssize_t ret;
unsigned long curr;
if (kstrtoul(buf, 0, &curr))
return -EINVAL;
if (curr > led->max_current)
return -EINVAL;
mutex_lock(&chip->lock);
ret = lp5523_write(chip->client,
LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
(u8)curr);
mutex_unlock(&chip->lock);
if (ret < 0)
return ret;
led->led_current = (u8)curr;
return len;
}
/* led class device attributes */
static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current);
static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
static struct attribute *lp5523_led_attributes[] = {
&dev_attr_led_current.attr,
&dev_attr_max_current.attr,
NULL,
};
static struct attribute_group lp5523_led_attribute_group = {
.attrs = lp5523_led_attributes
};
/* device attributes */
static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUSR,
show_engine1_mode, store_engine1_mode);
static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUSR,
show_engine2_mode, store_engine2_mode);
static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUSR,
show_engine3_mode, store_engine3_mode);
static DEVICE_ATTR(engine1_leds, S_IRUGO | S_IWUSR,
show_engine1_leds, store_engine1_leds);
static DEVICE_ATTR(engine2_leds, S_IRUGO | S_IWUSR,
show_engine2_leds, store_engine2_leds);
static DEVICE_ATTR(engine3_leds, S_IRUGO | S_IWUSR,
show_engine3_leds, store_engine3_leds);
static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load);
static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load);
static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load);
static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL);
static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr,
&dev_attr_engine2_mode.attr,
&dev_attr_engine3_mode.attr,
&dev_attr_selftest.attr,
&dev_attr_engine1_load.attr,
&dev_attr_engine1_leds.attr,
&dev_attr_engine2_load.attr,
&dev_attr_engine2_leds.attr,
&dev_attr_engine3_load.attr,
&dev_attr_engine3_leds.attr,
NULL,
};
......@@ -780,252 +404,91 @@ static const struct attribute_group lp5523_group = {
.attrs = lp5523_attributes,
};
static int lp5523_register_sysfs(struct i2c_client *client)
{
struct device *dev = &client->dev;
int ret;
ret = sysfs_create_group(&dev->kobj, &lp5523_group);
if (ret < 0)
return ret;
return 0;
}
static void lp5523_unregister_sysfs(struct i2c_client *client)
{
struct lp5523_chip *chip = i2c_get_clientdata(client);
struct device *dev = &client->dev;
int i;
sysfs_remove_group(&dev->kobj, &lp5523_group);
for (i = 0; i < chip->num_leds; i++)
sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
&lp5523_led_attribute_group);
}
/*--------------------------------------------------------------*/
/* Set chip operating mode */
/*--------------------------------------------------------------*/
static void lp5523_set_mode(struct lp5523_engine *engine, u8 mode)
{
/* if in that mode already do nothing, except for run */
if (mode == engine->mode && mode != LP5523_CMD_RUN)
return;
switch (mode) {
case LP5523_CMD_RUN:
lp5523_run_program(engine);
break;
case LP5523_CMD_LOAD:
lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
break;
case LP5523_CMD_DISABLED:
lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
break;
default:
return;
}
engine->mode = mode;
}
/*--------------------------------------------------------------*/
/* Probe, Attach, Remove */
/*--------------------------------------------------------------*/
static int __init lp5523_init_engine(struct lp5523_engine *engine, int id)
{
if (id < 1 || id > LP5523_ENGINES)
return -1;
engine->id = id;
engine->engine_mask = LP5523_ENG_MASK_BASE >> SHIFT_MASK(id);
engine->prog_page = id - 1;
engine->mux_page = id + 2;
return 0;
}
static int lp5523_init_led(struct lp5523_led *led, struct device *dev,
int chan, struct lp5523_platform_data *pdata,
const char *chip_name)
{
char name[32];
int res;
if (chan >= LP5523_LEDS)
return -EINVAL;
if (pdata->led_config[chan].led_current) {
led->led_current = pdata->led_config[chan].led_current;
led->max_current = pdata->led_config[chan].max_current;
led->chan_nr = pdata->led_config[chan].chan_nr;
if (led->chan_nr >= LP5523_LEDS) {
dev_err(dev, "Use channel numbers between 0 and %d\n",
LP5523_LEDS - 1);
return -EINVAL;
}
if (pdata->led_config[chan].name) {
led->cdev.name = pdata->led_config[chan].name;
} else {
snprintf(name, sizeof(name), "%s:channel%d",
pdata->label ? : chip_name, chan);
led->cdev.name = name;
}
led->cdev.brightness_set = lp5523_set_brightness;
res = led_classdev_register(dev, &led->cdev);
if (res < 0) {
dev_err(dev, "couldn't register led on channel %d\n",
chan);
return res;
}
res = sysfs_create_group(&led->cdev.dev->kobj,
&lp5523_led_attribute_group);
if (res < 0) {
dev_err(dev, "couldn't register current attribute\n");
led_classdev_unregister(&led->cdev);
return res;
}
} else {
led->led_current = 0;
}
return 0;
}
/* Chip specific configurations */
static struct lp55xx_device_config lp5523_cfg = {
.reset = {
.addr = LP5523_REG_RESET,
.val = LP5523_RESET,
},
.enable = {
.addr = LP5523_REG_ENABLE,
.val = LP5523_ENABLE,
},
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
.brightness_work_fn = lp5523_led_brightness_work,
.set_led_current = lp5523_set_led_current,
.firmware_cb = lp5523_firmware_loaded,
.run_engine = lp5523_run_engine,
.dev_attr_group = &lp5523_group,
};
static int lp5523_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lp5523_chip *chip;
struct lp5523_platform_data *pdata;
int ret, i, led;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
i2c_set_clientdata(client, chip);
chip->client = client;
pdata = client->dev.platform_data;
int ret;
struct lp55xx_chip *chip;
struct lp55xx_led *led;
struct lp55xx_platform_data *pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "no platform data\n");
return -EINVAL;
}
mutex_init(&chip->lock);
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
led = devm_kzalloc(&client->dev,
sizeof(*led) * pdata->num_channels, GFP_KERNEL);
if (!led)
return -ENOMEM;
chip->cl = client;
chip->pdata = pdata;
chip->cfg = &lp5523_cfg;
if (pdata->setup_resources) {
ret = pdata->setup_resources();
if (ret < 0)
return ret;
}
mutex_init(&chip->lock);
if (pdata->enable) {
pdata->enable(0);
usleep_range(1000, 2000); /* Keep enable down at least 1ms */
pdata->enable(1);
usleep_range(1000, 2000); /* 500us abs min. */
}
i2c_set_clientdata(client, led);
lp5523_write(client, LP5523_REG_RESET, 0xff);
usleep_range(10000, 20000); /*
* Exact value is not available. 10 - 20ms
* appears to be enough for reset.
*/
ret = lp5523_detect(client);
ret = lp55xx_init_device(chip);
if (ret)
goto fail1;
goto err_init;
dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
/* Initialize engines */
for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
ret = lp5523_init_engine(&chip->engines[i], i + 1);
if (ret) {
dev_err(&client->dev, "error initializing engine\n");
goto fail1;
}
}
ret = lp5523_configure(client);
if (ret < 0) {
dev_err(&client->dev, "error configuring chip\n");
goto fail1;
}
/* Initialize leds */
chip->num_channels = pdata->num_channels;
chip->num_leds = 0;
led = 0;
for (i = 0; i < pdata->num_channels; i++) {
/* Do not initialize channels that are not connected */
if (pdata->led_config[i].led_current == 0)
continue;
INIT_WORK(&chip->leds[led].brightness_work,
lp5523_led_brightness_work);
ret = lp55xx_register_leds(led, chip);
if (ret)
goto err_register_leds;
ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata,
id->name);
ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "error initializing leds\n");
goto fail2;
dev_err(&client->dev, "registering sysfs failed\n");
goto err_register_sysfs;
}
chip->num_leds++;
chip->leds[led].id = led;
/* Set LED current */
lp5523_write(client,
LP5523_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
chip->leds[led].led_current);
led++;
}
return 0;
ret = lp5523_register_sysfs(client);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
goto fail2;
}
return ret;
fail2:
for (i = 0; i < chip->num_leds; i++) {
led_classdev_unregister(&chip->leds[i].cdev);
flush_work(&chip->leds[i].brightness_work);
}
fail1:
if (pdata->enable)
pdata->enable(0);
if (pdata->release_resources)
pdata->release_resources();
err_register_sysfs:
lp55xx_unregister_leds(led, chip);
err_register_leds:
lp55xx_deinit_device(chip);
err_init:
return ret;
}
static int lp5523_remove(struct i2c_client *client)
{
struct lp5523_chip *chip = i2c_get_clientdata(client);
int i;
/* Disable engine mode */
lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED);
struct lp55xx_led *led = i2c_get_clientdata(client);
struct lp55xx_chip *chip = led->chip;
lp5523_unregister_sysfs(client);
for (i = 0; i < chip->num_leds; i++) {
led_classdev_unregister(&chip->leds[i].cdev);
flush_work(&chip->leds[i].brightness_work);
}
lp5523_stop_engine(chip);
lp55xx_unregister_sysfs(chip);
lp55xx_unregister_leds(led, chip);
lp55xx_deinit_device(chip);
if (chip->pdata->enable)
chip->pdata->enable(0);
if (chip->pdata->release_resources)
chip->pdata->release_resources();
return 0;
}
......@@ -1049,5 +512,6 @@ static struct i2c_driver lp5523_driver = {
module_i2c_driver(lp5523_driver);
MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP5523 LED engine");
MODULE_LICENSE("GPL");
/*
* LP5521/LP5523/LP55231 Common Driver
*
* Copyright 2012 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.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.
*
* Derived from leds-lp5521.c, leds-lp5523.c
*/
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_data/leds-lp55xx.h>
#include "leds-lp55xx-common.h"
static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev)
{
return container_of(cdev, struct lp55xx_led, cdev);
}
static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
{
return cdev_to_lp55xx_led(dev_get_drvdata(dev));
}
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->reset.addr;
u8 val = cfg->reset.val;
/* no error checking here because no ACK from the device after reset */
lp55xx_write(chip, addr, val);
}
static int lp55xx_detect_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->enable.addr;
u8 val = cfg->enable.val;
int ret;
ret = lp55xx_write(chip, addr, val);
if (ret)
return ret;
usleep_range(1000, 2000);
ret = lp55xx_read(chip, addr, &val);
if (ret)
return ret;
if (val != cfg->enable.val)
return -ENODEV;
return 0;
}
static int lp55xx_post_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
if (!cfg->post_init_device)
return 0;
return cfg->post_init_device(chip);
}
static ssize_t lp55xx_show_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = dev_to_lp55xx_led(dev);
return sprintf(buf, "%d\n", led->led_current);
}
static ssize_t lp55xx_store_current(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = dev_to_lp55xx_led(dev);
struct lp55xx_chip *chip = led->chip;
unsigned long curr;
if (kstrtoul(buf, 0, &curr))
return -EINVAL;
if (curr > led->max_current)
return -EINVAL;
if (!chip->cfg->set_led_current)
return len;
mutex_lock(&chip->lock);
chip->cfg->set_led_current(led, (u8)curr);
mutex_unlock(&chip->lock);
return len;
}
static ssize_t lp55xx_show_max_current(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = dev_to_lp55xx_led(dev);
return sprintf(buf, "%d\n", led->max_current);
}
static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current,
lp55xx_store_current);
static DEVICE_ATTR(max_current, S_IRUGO , lp55xx_show_max_current, NULL);
static struct attribute *lp55xx_led_attributes[] = {
&dev_attr_led_current.attr,
&dev_attr_max_current.attr,
NULL,
};
static struct attribute_group lp55xx_led_attr_group = {
.attrs = lp55xx_led_attributes
};
static void lp55xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
led->brightness = (u8)brightness;
schedule_work(&led->brightness_work);
}
static int lp55xx_init_led(struct lp55xx_led *led,
struct lp55xx_chip *chip, int chan)
{
struct lp55xx_platform_data *pdata = chip->pdata;
struct lp55xx_device_config *cfg = chip->cfg;
struct device *dev = &chip->cl->dev;
char name[32];
int ret;
int max_channel = cfg->max_channel;
if (chan >= max_channel) {
dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
return -EINVAL;
}
if (pdata->led_config[chan].led_current == 0)
return 0;
led->led_current = pdata->led_config[chan].led_current;
led->max_current = pdata->led_config[chan].max_current;
led->chan_nr = pdata->led_config[chan].chan_nr;
if (led->chan_nr >= max_channel) {
dev_err(dev, "Use channel numbers between 0 and %d\n",
max_channel - 1);
return -EINVAL;
}
led->cdev.brightness_set = lp55xx_set_brightness;
if (pdata->led_config[chan].name) {
led->cdev.name = pdata->led_config[chan].name;
} else {
snprintf(name, sizeof(name), "%s:channel%d",
pdata->label ? : chip->cl->name, chan);
led->cdev.name = name;
}
/*
* register led class device for each channel and
* add device attributes
*/
ret = led_classdev_register(dev, &led->cdev);
if (ret) {
dev_err(dev, "led register err: %d\n", ret);
return ret;
}
ret = sysfs_create_group(&led->cdev.dev->kobj, &lp55xx_led_attr_group);
if (ret) {
dev_err(dev, "led sysfs err: %d\n", ret);
led_classdev_unregister(&led->cdev);
return ret;
}
return 0;
}
static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
{
struct lp55xx_chip *chip = context;
struct device *dev = &chip->cl->dev;
if (!fw) {
dev_err(dev, "firmware request failed\n");
goto out;
}
/* handling firmware data is chip dependent */
mutex_lock(&chip->lock);
chip->fw = fw;
if (chip->cfg->firmware_cb)
chip->cfg->firmware_cb(chip);
mutex_unlock(&chip->lock);
out:
/* firmware should be released for other channel use */
release_firmware(chip->fw);
}
static int lp55xx_request_firmware(struct lp55xx_chip *chip)
{
const char *name = chip->cl->name;
struct device *dev = &chip->cl->dev;
return request_firmware_nowait(THIS_MODULE, true, name, dev,
GFP_KERNEL, chip, lp55xx_firmware_loaded);
}
static ssize_t lp55xx_show_engine_select(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
return sprintf(buf, "%d\n", chip->engine_idx);
}
static ssize_t lp55xx_store_engine_select(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
unsigned long val;
int ret;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
/* select the engine to be run */
switch (val) {
case LP55XX_ENGINE_1:
case LP55XX_ENGINE_2:
case LP55XX_ENGINE_3:
mutex_lock(&chip->lock);
chip->engine_idx = val;
ret = lp55xx_request_firmware(chip);
mutex_unlock(&chip->lock);
break;
default:
dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
return -EINVAL;
}
if (ret) {
dev_err(dev, "request firmware err: %d\n", ret);
return ret;
}
return len;
}
static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
{
if (chip->cfg->run_engine)
chip->cfg->run_engine(chip, start);
}
static ssize_t lp55xx_store_engine_run(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
unsigned long val;
if (kstrtoul(buf, 0, &val))
return -EINVAL;
/* run or stop the selected engine */
if (val <= 0) {
lp55xx_run_engine(chip, false);
return len;
}
mutex_lock(&chip->lock);
lp55xx_run_engine(chip, true);
mutex_unlock(&chip->lock);
return len;
}
static DEVICE_ATTR(select_engine, S_IRUGO | S_IWUSR,
lp55xx_show_engine_select, lp55xx_store_engine_select);
static DEVICE_ATTR(run_engine, S_IWUSR, NULL, lp55xx_store_engine_run);
static struct attribute *lp55xx_engine_attributes[] = {
&dev_attr_select_engine.attr,
&dev_attr_run_engine.attr,
NULL,
};
static const struct attribute_group lp55xx_engine_attr_group = {
.attrs = lp55xx_engine_attributes,
};
int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(chip->cl, reg, val);
}
EXPORT_SYMBOL_GPL(lp55xx_write);
int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val)
{
s32 ret;
ret = i2c_smbus_read_byte_data(chip->cl, reg);
if (ret < 0)
return ret;
*val = ret;
return 0;
}
EXPORT_SYMBOL_GPL(lp55xx_read);
int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val)
{
int ret;
u8 tmp;
ret = lp55xx_read(chip, reg, &tmp);
if (ret)
return ret;
tmp &= ~mask;
tmp |= val & mask;
return lp55xx_write(chip, reg, tmp);
}
EXPORT_SYMBOL_GPL(lp55xx_update_bits);
int lp55xx_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata;
struct lp55xx_device_config *cfg;
struct device *dev = &chip->cl->dev;
int ret = 0;
WARN_ON(!chip);
pdata = chip->pdata;
cfg = chip->cfg;
if (!pdata || !cfg)
return -EINVAL;
if (pdata->setup_resources) {
ret = pdata->setup_resources();
if (ret < 0) {
dev_err(dev, "setup resoure err: %d\n", ret);
goto err;
}
}
if (pdata->enable) {
pdata->enable(0);
usleep_range(1000, 2000); /* Keep enable down at least 1ms */
pdata->enable(1);
usleep_range(1000, 2000); /* 500us abs min. */
}
lp55xx_reset_device(chip);
/*
* Exact value is not available. 10 - 20ms
* appears to be enough for reset.
*/
usleep_range(10000, 20000);
ret = lp55xx_detect_device(chip);
if (ret) {
dev_err(dev, "device detection err: %d\n", ret);
goto err;
}
/* chip specific initialization */
ret = lp55xx_post_init_device(chip);
if (ret) {
dev_err(dev, "post init device err: %d\n", ret);
goto err_post_init;
}
return 0;
err_post_init:
lp55xx_deinit_device(chip);
err:
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_init_device);
void lp55xx_deinit_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
if (pdata->enable)
pdata->enable(0);
if (pdata->release_resources)
pdata->release_resources();
}
EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
struct lp55xx_device_config *cfg = chip->cfg;
int num_channels = pdata->num_channels;
struct lp55xx_led *each;
u8 led_current;
int ret;
int i;
if (!cfg->brightness_work_fn) {
dev_err(&chip->cl->dev, "empty brightness configuration\n");
return -EINVAL;
}
for (i = 0; i < num_channels; i++) {
/* do not initialize channels that are not connected */
if (pdata->led_config[i].led_current == 0)
continue;
led_current = pdata->led_config[i].led_current;
each = led + i;
ret = lp55xx_init_led(each, chip, i);
if (ret)
goto err_init_led;
INIT_WORK(&each->brightness_work, cfg->brightness_work_fn);
chip->num_leds++;
each->chip = chip;
/* setting led current at each channel */
if (cfg->set_led_current)
cfg->set_led_current(each, led_current);
}
return 0;
err_init_led:
lp55xx_unregister_leds(led, chip);
return ret;
}
EXPORT_SYMBOL_GPL(lp55xx_register_leds);
void lp55xx_unregister_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
int i;
struct lp55xx_led *each;
for (i = 0; i < chip->num_leds; i++) {
each = led + i;
led_classdev_unregister(&each->cdev);
flush_work(&each->brightness_work);
}
}
EXPORT_SYMBOL_GPL(lp55xx_unregister_leds);
int lp55xx_register_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
struct lp55xx_device_config *cfg = chip->cfg;
int ret;
if (!cfg->run_engine || !cfg->firmware_cb)
goto dev_specific_attrs;
ret = sysfs_create_group(&dev->kobj, &lp55xx_engine_attr_group);
if (ret)
return ret;
dev_specific_attrs:
return cfg->dev_attr_group ?
sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0;
}
EXPORT_SYMBOL_GPL(lp55xx_register_sysfs);
void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
struct lp55xx_device_config *cfg = chip->cfg;
if (cfg->dev_attr_group)
sysfs_remove_group(&dev->kobj, cfg->dev_attr_group);
sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group);
}
EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP55xx Common Driver");
MODULE_LICENSE("GPL");
/*
* LP55XX Common Driver Header
*
* Copyright (C) 2012 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.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.
*
* Derived from leds-lp5521.c, leds-lp5523.c
*/
#ifndef _LEDS_LP55XX_COMMON_H
#define _LEDS_LP55XX_COMMON_H
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
LP55XX_ENGINE_2,
LP55XX_ENGINE_3,
};
struct lp55xx_led;
struct lp55xx_chip;
/*
* struct lp55xx_reg
* @addr : Register address
* @val : Register value
*/
struct lp55xx_reg {
u8 addr;
u8 val;
};
/*
* struct lp55xx_device_config
* @reset : Chip specific reset command
* @enable : Chip specific enable command
* @max_channel : Maximum number of channels
* @post_init_device : Chip specific initialization code
* @brightness_work_fn : Brightness work function
* @set_led_current : LED current set function
* @firmware_cb : Call function when the firmware is loaded
* @run_engine : Run internal engine for pattern
* @dev_attr_group : Device specific attributes
*/
struct lp55xx_device_config {
const struct lp55xx_reg reset;
const struct lp55xx_reg enable;
const int max_channel;
/* define if the device has specific initialization process */
int (*post_init_device) (struct lp55xx_chip *chip);
/* access brightness register */
void (*brightness_work_fn)(struct work_struct *work);
/* current setting function */
void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
/* access program memory when the firmware is loaded */
void (*firmware_cb)(struct lp55xx_chip *chip);
/* used for running firmware LED patterns */
void (*run_engine) (struct lp55xx_chip *chip, bool start);
/* additional device specific attributes */
const struct attribute_group *dev_attr_group;
};
/*
* struct lp55xx_chip
* @cl : I2C communication for access registers
* @pdata : Platform specific data
* @lock : Lock for user-space interface
* @num_leds : Number of registered LEDs
* @cfg : Device specific configuration data
* @engine_idx : Selected engine number
* @fw : Firmware data for running a LED pattern
*/
struct lp55xx_chip {
struct i2c_client *cl;
struct lp55xx_platform_data *pdata;
struct mutex lock; /* lock for user-space interface */
int num_leds;
struct lp55xx_device_config *cfg;
enum lp55xx_engine_index engine_idx;
const struct firmware *fw;
};
/*
* struct lp55xx_led
* @chan_nr : Channel number
* @cdev : LED class device
* @led_current : Current setting at each led channel
* @max_current : Maximun current at each led channel
* @brightness_work : Workqueue for brightness control
* @brightness : Brightness value
* @chip : The lp55xx chip data
*/
struct lp55xx_led {
int chan_nr;
struct led_classdev cdev;
u8 led_current;
u8 max_current;
struct work_struct brightness_work;
u8 brightness;
struct lp55xx_chip *chip;
};
/* register access */
extern int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val);
extern int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val);
extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg,
u8 mask, u8 val);
/* common device init/deinit functions */
extern int lp55xx_init_device(struct lp55xx_chip *chip);
extern void lp55xx_deinit_device(struct lp55xx_chip *chip);
/* common LED class device functions */
extern int lp55xx_register_leds(struct lp55xx_led *led,
struct lp55xx_chip *chip);
extern void lp55xx_unregister_leds(struct lp55xx_led *led,
struct lp55xx_chip *chip);
/* common device attributes functions */
extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
#endif /* _LEDS_LP55XX_COMMON_H */
......@@ -130,9 +130,10 @@ static int lp8788_led_probe(struct platform_device *pdev)
struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);
struct lp8788_led_platform_data *led_pdata;
struct lp8788_led *led;
struct device *dev = &pdev->dev;
int ret;
led = devm_kzalloc(lp->dev, sizeof(struct lp8788_led), GFP_KERNEL);
led = devm_kzalloc(dev, sizeof(struct lp8788_led), GFP_KERNEL);
if (!led)
return -ENOMEM;
......@@ -154,13 +155,13 @@ static int lp8788_led_probe(struct platform_device *pdev)
ret = lp8788_led_init_device(led, led_pdata);
if (ret) {
dev_err(lp->dev, "led init device err: %d\n", ret);
dev_err(dev, "led init device err: %d\n", ret);
return ret;
}
ret = led_classdev_register(lp->dev, &led->led_dev);
ret = led_classdev_register(dev, &led->led_dev);
if (ret) {
dev_err(lp->dev, "led register err: %d\n", ret);
dev_err(dev, "led register err: %d\n", ret);
return ret;
}
......
......@@ -311,7 +311,6 @@ static int pca9532_destroy_devices(struct pca9532_data *data, int n_devs)
break;
case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) {
input_unregister_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
}
......@@ -382,7 +381,7 @@ static int pca9532_configure(struct i2c_client *client,
BUG_ON(data->idev);
led->state = PCA9532_PWM1;
pca9532_setled(led);
data->idev = input_allocate_device();
data->idev = devm_input_allocate_device(&client->dev);
if (data->idev == NULL) {
err = -ENOMEM;
goto exit;
......@@ -401,7 +400,6 @@ static int pca9532_configure(struct i2c_client *client,
INIT_WORK(&data->work, pca9532_input_work);
err = input_register_device(data->idev);
if (err) {
input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
goto exit;
......
......@@ -16,6 +16,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/fb.h>
#include <linux/leds.h>
#include <linux/err.h>
......@@ -30,6 +31,11 @@ struct led_pwm_data {
unsigned int period;
};
struct led_pwm_priv {
int num_leds;
struct led_pwm_data leds[0];
};
static void led_pwm_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
......@@ -47,32 +53,94 @@ static void led_pwm_set(struct led_classdev *led_cdev,
}
}
static inline size_t sizeof_pwm_leds_priv(int num_leds)
{
return sizeof(struct led_pwm_priv) +
(sizeof(struct led_pwm_data) * num_leds);
}
static struct led_pwm_priv *led_pwm_create_of(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device_node *child;
struct led_pwm_priv *priv;
int count, ret;
/* count LEDs in this device, so we know how much to allocate */
count = of_get_child_count(node);
if (!count)
return NULL;
priv = devm_kzalloc(&pdev->dev, sizeof_pwm_leds_priv(count),
GFP_KERNEL);
if (!priv)
return NULL;
for_each_child_of_node(node, child) {
struct led_pwm_data *led_dat = &priv->leds[priv->num_leds];
led_dat->cdev.name = of_get_property(child, "label",
NULL) ? : child->name;
led_dat->pwm = devm_of_pwm_get(&pdev->dev, child, NULL);
if (IS_ERR(led_dat->pwm)) {
dev_err(&pdev->dev, "unable to request PWM for %s\n",
led_dat->cdev.name);
goto err;
}
/* Get the period from PWM core when n*/
led_dat->period = pwm_get_period(led_dat->pwm);
led_dat->cdev.default_trigger = of_get_property(child,
"linux,default-trigger", NULL);
of_property_read_u32(child, "max-brightness",
&led_dat->cdev.max_brightness);
led_dat->cdev.brightness_set = led_pwm_set;
led_dat->cdev.brightness = LED_OFF;
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register for %s\n",
led_dat->cdev.name);
of_node_put(child);
goto err;
}
priv->num_leds++;
}
return priv;
err:
while (priv->num_leds--)
led_classdev_unregister(&priv->leds[priv->num_leds].cdev);
return NULL;
}
static int led_pwm_probe(struct platform_device *pdev)
{
struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
struct led_pwm *cur_led;
struct led_pwm_data *leds_data, *led_dat;
struct led_pwm_priv *priv;
int i, ret = 0;
if (!pdata)
return -EBUSY;
leds_data = devm_kzalloc(&pdev->dev,
sizeof(struct led_pwm_data) * pdata->num_leds,
if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev,
sizeof_pwm_leds_priv(pdata->num_leds),
GFP_KERNEL);
if (!leds_data)
if (!priv)
return -ENOMEM;
for (i = 0; i < pdata->num_leds; i++) {
cur_led = &pdata->leds[i];
led_dat = &leds_data[i];
struct led_pwm *cur_led = &pdata->leds[i];
struct led_pwm_data *led_dat = &priv->leds[i];
led_dat->pwm = pwm_request(cur_led->pwm_id,
cur_led->name);
led_dat->pwm = devm_pwm_get(&pdev->dev, cur_led->name);
if (IS_ERR(led_dat->pwm)) {
ret = PTR_ERR(led_dat->pwm);
dev_err(&pdev->dev, "unable to request PWM %d\n",
cur_led->pwm_id);
dev_err(&pdev->dev,
"unable to request PWM for %s\n",
cur_led->name);
goto err;
}
......@@ -86,49 +154,51 @@ static int led_pwm_probe(struct platform_device *pdev)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0) {
pwm_free(led_dat->pwm);
if (ret < 0)
goto err;
}
priv->num_leds = pdata->num_leds;
} else {
priv = led_pwm_create_of(pdev);
if (!priv)
return -ENODEV;
}
platform_set_drvdata(pdev, leds_data);
platform_set_drvdata(pdev, priv);
return 0;
err:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
led_classdev_unregister(&leds_data[i].cdev);
pwm_free(leds_data[i].pwm);
}
}
while (i--)
led_classdev_unregister(&priv->leds[i].cdev);
return ret;
}
static int led_pwm_remove(struct platform_device *pdev)
{
struct led_pwm_priv *priv = platform_get_drvdata(pdev);
int i;
struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
struct led_pwm_data *leds_data;
leds_data = platform_get_drvdata(pdev);
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&leds_data[i].cdev);
pwm_free(leds_data[i].pwm);
}
for (i = 0; i < priv->num_leds; i++)
led_classdev_unregister(&priv->leds[i].cdev);
return 0;
}
static const struct of_device_id of_pwm_leds_match[] = {
{ .compatible = "pwm-leds", },
{},
};
MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
static struct platform_driver led_pwm_driver = {
.probe = led_pwm_probe,
.remove = led_pwm_remove,
.driver = {
.name = "leds_pwm",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_pwm_leds_match),
},
};
......
......@@ -133,24 +133,24 @@ static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness)
rate = clk_get_rate(p->clk);
/* pick the lowest acceptable rate */
for (k = 0; k < ARRAY_SIZE(prescaler); k++)
if ((rate / prescaler[k]) < p->min_rate)
for (k = ARRAY_SIZE(prescaler) - 1; k >= 0; k--)
if ((rate / prescaler[k]) >= p->min_rate)
break;
if (!k) {
if (k < 0) {
dev_err(&p->pdev->dev, "clock rate mismatch\n");
goto err0;
}
dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n",
rate, prescaler[k - 1]);
rate, prescaler[k]);
/* clear TCNT on TGRB match, count on rising edge, set prescaler */
r_tpu_write(p, TCR, 0x0040 | (k - 1));
r_tpu_write(p, TCR, 0x0040 | k);
/* output 0 until TGRA, output 1 until TGRB */
r_tpu_write(p, TIOR, 0x0002);
rate /= prescaler[k - 1] * p->refresh_rate;
rate /= prescaler[k] * p->refresh_rate;
r_tpu_write(p, TGRB, rate);
dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate);
......
......@@ -63,8 +63,7 @@ MODULE_LICENSE("GPL");
/*
* PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives.
*/
static const struct pci_device_id ich7_lpc_pci_id[] =
{
static DEFINE_PCI_DEVICE_TABLE(ich7_lpc_pci_id) = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) },
......
......@@ -3,6 +3,8 @@
* Copyright (C) 2008 David S. Miller <davem@davemloft.net>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
......@@ -14,9 +16,6 @@
#include <asm/fhc.h>
#include <asm/upa.h>
#define DRIVER_NAME "leds-sunfire"
#define PFX DRIVER_NAME ": "
MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
MODULE_DESCRIPTION("Sun Fire LED driver");
MODULE_LICENSE("GPL");
......@@ -130,14 +129,14 @@ static int sunfire_led_generic_probe(struct platform_device *pdev,
int i, err;
if (pdev->num_resources != 1) {
printk(KERN_ERR PFX "Wrong number of resources %d, should be 1\n",
dev_err(&pdev->dev, "Wrong number of resources %d, should be 1\n",
pdev->num_resources);
return -EINVAL;
}
p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
if (!p) {
printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata\n");
dev_err(&pdev->dev, "Could not allocate struct sunfire_drvdata\n");
return -ENOMEM;
}
......@@ -152,7 +151,7 @@ static int sunfire_led_generic_probe(struct platform_device *pdev,
err = led_classdev_register(&pdev->dev, lp);
if (err) {
printk(KERN_ERR PFX "Could not register %s LED\n",
dev_err(&pdev->dev, "Could not register %s LED\n",
lp->name);
for (i--; i >= 0; i--)
led_classdev_unregister(&p->leds[i].led_cdev);
......@@ -188,7 +187,7 @@ static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "clockboard-right",
.handler = clockboard_right_set,
.default_trigger= "heartbeat",
.default_trigger = "heartbeat",
},
};
......@@ -209,7 +208,7 @@ static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "fhc-right",
.handler = fhc_right_set,
.default_trigger= "heartbeat",
.default_trigger = "heartbeat",
},
};
......@@ -244,13 +243,13 @@ static int __init sunfire_leds_init(void)
int err = platform_driver_register(&sunfire_clockboard_led_driver);
if (err) {
printk(KERN_ERR PFX "Could not register clock board LED driver\n");
pr_err("Could not register clock board LED driver\n");
return err;
}
err = platform_driver_register(&sunfire_fhc_led_driver);
if (err) {
printk(KERN_ERR PFX "Could not register FHC LED driver\n");
pr_err("Could not register FHC LED driver\n");
platform_driver_unregister(&sunfire_clockboard_led_driver);
}
......
......@@ -667,6 +667,66 @@ static void tca6507_remove_gpio(struct tca6507_chip *tca)
}
#endif /* CONFIG_GPIOLIB */
#ifdef CONFIG_OF
static struct tca6507_platform_data *
tca6507_led_dt_init(struct i2c_client *client)
{
struct device_node *np = client->dev.of_node, *child;
struct tca6507_platform_data *pdata;
struct led_info *tca_leds;
int count;
count = of_get_child_count(np);
if (!count || count > NUM_LEDS)
return ERR_PTR(-ENODEV);
tca_leds = devm_kzalloc(&client->dev,
sizeof(struct led_info) * count, GFP_KERNEL);
if (!tca_leds)
return ERR_PTR(-ENOMEM);
for_each_child_of_node(np, child) {
struct led_info led;
u32 reg;
int ret;
led.name =
of_get_property(child, "label", NULL) ? : child->name;
led.default_trigger =
of_get_property(child, "linux,default-trigger", NULL);
ret = of_property_read_u32(child, "reg", &reg);
if (ret != 0)
continue;
tca_leds[reg] = led;
}
pdata = devm_kzalloc(&client->dev,
sizeof(struct tca6507_platform_data), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->leds.leds = tca_leds;
pdata->leds.num_leds = count;
return pdata;
}
static const struct of_device_id of_tca6507_leds_match[] = {
{ .compatible = "ti,tca6507", },
{},
};
#else
static struct tca6507_platform_data *
tca6507_led_dt_init(struct i2c_client *client)
{
return ERR_PTR(-ENODEV);
}
#define of_tca6507_leds_match NULL
#endif
static int tca6507_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
......@@ -683,9 +743,12 @@ static int tca6507_probe(struct i2c_client *client,
return -EIO;
if (!pdata || pdata->leds.num_leds != NUM_LEDS) {
pdata = tca6507_led_dt_init(client);
if (IS_ERR(pdata)) {
dev_err(&client->dev, "Need %d entries in platform-data list\n",
NUM_LEDS);
return -ENODEV;
return PTR_ERR(pdata);
}
}
tca = devm_kzalloc(&client->dev, sizeof(*tca), GFP_KERNEL);
if (!tca)
......@@ -750,6 +813,7 @@ static struct i2c_driver tca6507_driver = {
.driver = {
.name = "leds-tca6507",
.owner = THIS_MODULE,
.of_match_table = of_tca6507_leds_match,
},
.probe = tca6507_probe,
.remove = tca6507_remove,
......
......@@ -157,7 +157,7 @@ static int wm831x_status_blink_set(struct led_classdev *led_cdev,
return ret;
}
static const char *led_src_texts[] = {
static const char * const led_src_texts[] = {
"otp",
"power",
"charger",
......
......@@ -471,7 +471,7 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
}
/**
* of_pwm_request() - request a PWM via the PWM framework
* of_pwm_get() - request a PWM via the PWM framework
* @np: device node to get the PWM from
* @con_id: consumer name
*
......@@ -486,8 +486,7 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np)
* becomes mandatory for devices that look up the PWM device via the con_id
* parameter.
*/
static struct pwm_device *of_pwm_request(struct device_node *np,
const char *con_id)
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
{
struct pwm_device *pwm = NULL;
struct of_phandle_args args;
......@@ -545,6 +544,7 @@ static struct pwm_device *of_pwm_request(struct device_node *np,
return pwm;
}
EXPORT_SYMBOL_GPL(of_pwm_get);
/**
* pwm_add_table() - register PWM device consumers
......@@ -587,7 +587,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id)
/* look up via DT first */
if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node)
return of_pwm_request(dev->of_node, con_id);
return of_pwm_get(dev->of_node, con_id);
/*
* We look up the provider in the static table typically provided by
......@@ -708,6 +708,36 @@ struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
}
EXPORT_SYMBOL_GPL(devm_pwm_get);
/**
* devm_of_pwm_get() - resource managed of_pwm_get()
* @dev: device for PWM consumer
* @np: device node to get the PWM from
* @con_id: consumer name
*
* This function performs like of_pwm_get() but the acquired PWM device will
* automatically be released on driver detach.
*/
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
const char *con_id)
{
struct pwm_device **ptr, *pwm;
ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
pwm = of_pwm_get(np, con_id);
if (!IS_ERR(pwm)) {
*ptr = pwm;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return pwm;
}
EXPORT_SYMBOL_GPL(devm_of_pwm_get);
static int devm_pwm_match(struct device *dev, void *res, void *data)
{
struct pwm_device **p = res;
......
/*
* LP5523 LED Driver
*
* Copyright (C) 2010 Nokia Corporation
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.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.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef __LINUX_LP5523_H
#define __LINUX_LP5523_H
/* See Documentation/leds/leds-lp5523.txt */
struct lp5523_led_config {
const char *name;
u8 chan_nr;
u8 led_current; /* mA x10, 0 if led is not connected */
u8 max_current;
};
#define LP5523_CLOCK_AUTO 0
#define LP5523_CLOCK_INT 1
#define LP5523_CLOCK_EXT 2
struct lp5523_platform_data {
struct lp5523_led_config *led_config;
u8 num_channels;
u8 clock_mode;
int (*setup_resources)(void);
void (*release_resources)(void);
void (*enable)(bool state);
const char *label;
};
#endif /* __LINUX_LP5523_H */
......@@ -7,7 +7,7 @@
struct led_pwm {
const char *name;
const char *default_trigger;
unsigned pwm_id;
unsigned pwm_id __deprecated;
u8 active_low;
unsigned max_brightness;
unsigned pwm_period_ns;
......
/*
* LP5521 LED chip driver.
* LP55XX Platform Data Header
*
* Copyright (C) 2010 Nokia Corporation
* Copyright (C) 2012 Texas Instruments
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
* Author: Milo(Woogyom) Kim <milo.kim@ti.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.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
* Derived from leds-lp5521.h, leds-lp5523.h
*/
#ifndef __LINUX_LP5521_H
#define __LINUX_LP5521_H
#ifndef _LEDS_LP55XX_H
#define _LEDS_LP55XX_H
/* Clock configuration */
#define LP55XX_CLOCK_AUTO 0
#define LP55XX_CLOCK_INT 1
#define LP55XX_CLOCK_EXT 2
/* See Documentation/leds/leds-lp5521.txt */
/* Bits in LP5521 CONFIG register. 'update_config' in lp55xx_platform_data */
#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */
#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */
#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */
#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */
#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */
#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */
#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */
#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */
#define LP5521_CLK_INT 1 /* Internal clock */
#define LP5521_CLK_AUTO 2 /* Automatic clock selection */
struct lp5521_led_config {
char *name;
struct lp55xx_led_config {
const char *name;
u8 chan_nr;
u8 led_current; /* mA x10, 0 if led is not connected */
u8 max_current;
};
struct lp5521_led_pattern {
struct lp55xx_predef_pattern {
u8 *r;
u8 *g;
u8 *b;
......@@ -41,33 +48,40 @@ struct lp5521_led_pattern {
u8 size_b;
};
#define LP5521_CLOCK_AUTO 0
#define LP5521_CLOCK_INT 1
#define LP5521_CLOCK_EXT 2
/* Bits in CONFIG register */
#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */
#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */
#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */
#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */
#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */
#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */
#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */
#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */
#define LP5521_CLK_INT 1 /* Internal clock */
#define LP5521_CLK_AUTO 2 /* Automatic clock selection */
/*
* struct lp55xx_platform_data
* @led_config : Configurable led class device
* @num_channels : Number of LED channels
* @label : Used for naming LEDs
* @clock_mode : Input clock mode. LP55XX_CLOCK_AUTO or _INT or _EXT
* @setup_resources : Platform specific function before enabling the chip
* @release_resources : Platform specific function after disabling the chip
* @enable : EN pin control by platform side
* @patterns : Predefined pattern data for RGB channels
* @num_patterns : Number of patterns
* @update_config : Value of CONFIG register
*/
struct lp55xx_platform_data {
struct lp5521_platform_data {
struct lp5521_led_config *led_config;
/* LED channel configuration */
struct lp55xx_led_config *led_config;
u8 num_channels;
const char *label;
/* Clock configuration */
u8 clock_mode;
/* Platform specific functions */
int (*setup_resources)(void);
void (*release_resources)(void);
void (*enable)(bool state);
const char *label;
/* Predefined pattern data */
struct lp55xx_predef_pattern *patterns;
unsigned int num_patterns;
/* _CONFIG register */
u8 update_config;
struct lp5521_led_pattern *patterns;
int num_patterns;
};
#endif /* __LINUX_LP5521_H */
#endif /* _LEDS_LP55XX_H */
......@@ -174,10 +174,13 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip,
struct pwm_device *of_pwm_xlate_with_flags(struct pwm_chip *pc,
const struct of_phandle_args *args);
struct pwm_device *pwm_get(struct device *dev, const char *consumer);
struct pwm_device *pwm_get(struct device *dev, const char *con_id);
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id);
void pwm_put(struct pwm_device *pwm);
struct pwm_device *devm_pwm_get(struct device *dev, const char *consumer);
struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id);
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,
const char *con_id);
void devm_pwm_put(struct device *dev, struct pwm_device *pwm);
#else
static inline int pwm_set_chip_data(struct pwm_device *pwm, void *data)
......@@ -213,6 +216,12 @@ static inline struct pwm_device *pwm_get(struct device *dev,
return ERR_PTR(-ENODEV);
}
static inline struct pwm_device *of_pwm_get(struct device_node *np,
const char *con_id)
{
return ERR_PTR(-ENODEV);
}
static inline void pwm_put(struct pwm_device *pwm)
{
}
......@@ -223,6 +232,13 @@ static inline struct pwm_device *devm_pwm_get(struct device *dev,
return ERR_PTR(-ENODEV);
}
static inline struct pwm_device *devm_of_pwm_get(struct device *dev,
struct device_node *np,
const char *con_id)
{
return ERR_PTR(-ENODEV);
}
static inline void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
{
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment