This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

LP5569: LP5569

Part Number: LP5569
Other Parts Discussed in Thread: LP5523,

Hi - I am not sure this is the right forum for my issue, but this is where TI sent me. 

I am updating a linux driver I found that had a few bugs. It is functioning fine for the most part - it can individually control LEDs as well as run a single engine with a custom program. 


The problem is when I try to have more than one engine running. I can see the correct data is sent to the chip on the i2c interface. These are the steps I followed.
- set engine1_mode to "load"
- load program to engine1_load (this program pulses a red LED)
- set engine1_mode to "run"
At this point the intended red LED pulses as programmed. I then program the second engine as follows.
- set engine2_mode to "load"
- load program to engine2_mode (this program pulses a green LED)
- set engine2_mode to "run"
At this point, the same red LED pulses. It seems to be running the program for engine1.

Besides the above trouble, there are a couple of points I need clarification on.
1. I am using the LP5523 LASM assembler to generate my program. When I have more than one program and have an LED map table at the top, do I include the table when programming each engine? I have tried programming with the table just for the first engine and also for each engine. But, I didn't see any difference in behavior.
2. In LP5523 datasheet, the information for ENGINE_CONTROL2 implies that all engines are put in HOLD state whenever any engine is in load mode. It is not clear if just setting an engine mode to load will automatically set all engines in HOLD. But, in LP5569, it states "If any engine is set to the load-program mode, then the other engines should be set either to the
disabled or load-program mode, because they are inhibited from executing instructions while loading the SRAM". From this I assume, one has to manually set any running engines to either disabled or load mode. Is this correct?

Best regards,

Daniel

  • Hi Daniel,

    Could you help to share your engine code?

    1. Yes if you need to control multiple channels by engine, the LED map table need to be included on the top. If you only control one channel, map_sel can be used so do not need the table.

    2. Yes you need to manually set the running engine to disabled or load-program mode.

  • Hi Hardy,

    Thank you for responding. Sorry for the late replay. This is the engine source code and it compiles fine using the LP5523 assembled

    red: dw 0000000100000000b
    grn: dw 0000000000010000b
    blu: dw 0000000000000001b

    .segment program1 ;Beginning of a segment
    mux_map_addr red ;mapping table start address; first row is activated
    loop1: ramp 2.0 255 ;beginning of a loop, set PWM full scale
    ramp 2.0 -255
    ramp 2.0 -255
    branch 0 loop1 ;endless loop
    rst

    .segment program2 ;Beginning of a segment
    mux_map_addr grn ;mapping table start address; first row is activated
    wait 0.45
    loop2: ramp 2.0 -255 ;beginning of a loop, set PWM full scale
    ramp 2.0 255
    ramp 2.0 -255
    branch 0 loop2 ;endless loop
    rst

    .segment program3 ;Beginning of a segment
    mux_map_addr blu ;mapping table start address; first row is activated
    wait 0.45
    wait 0.45
    loop3: ramp 2.0 -255 ;beginning of a loop, set PWM full scale
    ramp 2.0 -255
    ramp 2.0 255
    branch 0 loop3 ;endless loop
    rst

    This is the hex file

    01 00 00 10 00 01 9F 80 20 FF 21 FF 21 FF A0 01
    00 00 9F 81 7A 00 21 FF 20 FF 21 FF A0 02 00 00
    9F 82 7A 00 7A 00 21 FF 21 FF 20 FF A0 03 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    @ 03 program1
    @ 09 program2
    @ 10 program3

    Btw, I am not using the MSP430 board for programming it. I am directly using the driver for each engine. So, I am still not sure what you mean by adding the table at the top. When I program engine1, I select the engine and load the program including the map table. What is still not clear to me is that when I program engine2 and engine3, do I still need to add the map table for each engine before the program? I didn't see any difference whether I added the table or not. 

    Regards,

    Daniel

  • Hi Daniel,

    Sorry that I missed the notification. I mentioned the table is

    'red: dw 0000000100000000b
    grn: dw 0000000000010000b
    blu: dw 0000000000000001b'

    which used to map the related action channels. It need to be added at the front of whole program but do not need to be added before engine2 and engine3. The engine code is OK to control different channels. And I also verified on EVM.

    So the issue is still engine2 and engine3 controls the red LED right? Could you help to share your schematic and the detail register setting procedure of 

    '- set engine1_mode to "load"
    - load program to engine1_load (this program pulses a red LED)
    - set engine1_mode to "run"
    At this point the intended red LED pulses as programmed. I then program the second engine as follows.
    - set engine2_mode to "load"
    - load program to engine2_mode (this program pulses a green LED)
    - set engine2_mode to "run"
    At this point, the same red LED pulses. It seems to be running the program for engine1.' ?

  •  Hi Hardy,

    Thank you for your response. See below for my answer highlighted 

    Hardy Wu replied to LP5569: LP5569.

    Hi Daniel,

    Sorry that I missed the notification. I mentioned the table is

    'red: dw 0000000100000000b
    grn: dw 0000000000010000b
    blu: dw 0000000000000001b'

    which used to map the related action channels. It need to be added at the front of whole program but do not need to be added before engine2 and engine3. ​I don't understand what you mean by adding at the front of the whole program. Using the i2c interface, I can only load the three programs and three LED map values. By adding at the front, do you mean to add it to engine1's program? The engine code is OK to control different channels. And I also verified on EVM. ​Were you able to run all three engines at the same time and see the correct LED behavior? It isn't clear whether you had success with all engines running. One thing I haven't done is stop other running engines when loading an engine. Could this be causing the issue? 

    So the issue is still engine2 and engine3 controls the red LED right? Could you help to share your schematic There is no schematic to speak of. I just wired LP5569EVM to the i2c interface on my board. I have run different program on engine 1 to test all the LEDs and that checks all the LEDs are in working order.

    and the detail register setting procedure of 

    '- set engine1_mode to "load"        ​echo load > engine1_mode
    - load program to engine1_load (this program pulses a red LED)      echo "0010xxxxx...." > engine1_load
    - set engine1_mode to "run"         ​echo run > engine1_mode
    At this point the intended red LED pulses as programmed. I then program the second engine as follows.
    - set engine2_mode to "load"       ​echo load > engine2_mode
    - load program to engine2_mode (this program pulses a green LED)    ​echo "hex program" > engine2_mode
    - set engine2_mode to "run"         ​echo run > engine2_mode
    At this point, the same red LED pulses. It seems to be running the program for engine1.' ?

     

  • Hi Daniel,

     ​I don't understand what you mean by adding at the front of the whole program. Using the i2c interface, I can only load the three programs and three LED map values. By adding at the front, do you mean to add it to engine1's program?
    Sorry for the confusion and please just forget this point. Your code is right. What I tried to said is this three dw commands, which declared the short name of execution output channels, need to be in front of whole program.   

    Were you able to run all three engines at the same time and see the correct LED behavior? It isn't clear whether you had success with all engines running. One thing I haven't done is stop other running engines when loading an engine. Could this be causing the issue? 
    Yes I run all three them at the same time and they can run independently as code described. Did you try only run engine2 firstly and see if it can control LEDs? 

    For detail register setting procedure
    I have no driver code for your program so I cannot sure what exactly registers were set by your commands. One thing need to be confirm is that I saw there are two 'echo "0010xxxxx....">engine1/2_load' commands. Is this means that you upload whole hex program twice? Or divide hex program into 3 parts and separately uploaded them?  
    What I usually do is 1) set all engines to load mode (write 0x54 to register 0x02) 2) upload whole hex program into SRAM 3) set all engines to run mode (write 0xA8 to register 0x02) 4) execute engines (write 0xA8 to register 0x01)

  • Hi Hardy

    Sorry for the confusion and please just forget this point. Your code is right. What I tried to said is this three dw commands, which declared the short name of execution output channels, need to be in front of whole program. --- This is the part I am still not getting. How do I put it in front of the whole program?

    One thing need to be confirm is that I saw there are two 'echo "0010xxxxx....">engine1/2_load' commands. Is this means that you upload whole hex program twice? Or divide hex program into 3 parts and separately uploaded them? --- If I am programming multiple engines, I upload each engine's portion separately. For the program you loaded and tested, that was broken into three separate loads for each engine. The first engine program always includes the map table. For the next two engines, I tried loading with and without the program. 

    What I usually do is 1) set all engines to load mode (write 0x54 to register 0x02) 2) upload whole hex program into SRAM 3) set all engines to run mode (write 0xA8 to register 0x02) 4) execute engines (write 0xA8 to register 0x01) --- I am not clear about step 2. I use echo "xxxx..." > enginex_load command. What address do I upload the whole program to? 

    Attached is the driver source.

    leds-lp5569.c
    /*
     * lp5569.c - LP5569LED 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>
     *          Woonyong, Yun <wyyun@humaxdigital.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
     */
    
    #include <linux/delay.h>
    #include <linux/firmware.h>
    #include <linux/i2c.h>
    #include <linux/leds.h>
    #include <linux/module.h>
    #include <linux/mutex.h>
    #include <linux/of.h>
    #include <linux/platform_data/leds-lp55xx.h>
    #include <linux/slab.h>
    
    #include "leds-lp55xx-common.h"
    
    #define LP5569_PROGRAM_LENGTH		32	/* bytes */
    /* Memory is used like this:
       0x00 engine 1 program
       0x10 engine 2 program
       0x20 engine 3 program
       0x30 engine 1 muxing info
       0x40 engine 2 muxing info
       0x50 engine 3 muxing info
    */
    #define LP5569_MAX_LEDS			9
    
    /* Registers */
    #define LP5569_REG_ENABLE		0x00	/*same*/
    #define LP5569_REG_OP_EXEC		0x01
    #define LP5569_REG_OP_MODE		0x02	/*0x01 with different bitfields*/
    #define LP5569_REG_ENABLE_LEDS_MSB	0x04
    #define LP5569_REG_ENABLE_LEDS_LSB	0x05
    #define LP5569_REG_LED_CTRL_BASE	0x07	/*0x06*/
    #define LP5569_REG_LED_PWM_BASE		0x16	/*same*/
    #define LP5569_REG_LED_CURRENT_BASE	0x22	/*0x26*/
    #define LP5569_REG_CONFIG		0x2F	/*0x36*/
    #define LP5569_REG_STATUS		0x3C	/*0x3A*/
    #define LP5569_REG_RESET		0x3F	/*0x3D*/
    #define LP5569_REG_LED_TEST_CTRL	0x41
    #define LP5569_REG_LED_TEST_ADC		0x42
    #define LP5569_REG_MASTER_FADER_BASE	0x46	/*0x48*/
    #define LP5569_REG_CH1_PROG_START	0x4B	/*0x4C*/
    #define LP5569_REG_CH2_PROG_START	0x4C	/*0x4D*/
    #define LP5569_REG_CH3_PROG_START	0x4D	/*0x4E*/
    #define LP5569_REG_PROG_PAGE_SEL	0x4F	/*same*/
    #define LP5569_REG_PROG_MEM		0x50	/*same*/
    
    /* Bit description in registers */
    #define LP5569_ENABLE			0x40
    #define LP5569_AUTO_INC			0x40
    #define LP5569_PWR_SAVE			0x20
    #define LP5569_PWM_PWR_SAVE		0x04
    #define LP5569_CP_AUTO			0x18
    #define LP5569_AUTO_CLK			0x02
    #define LP5569_INTERNAL_CLK		0x01
    
    #define LP5569_EN_LEDTEST		0x80
    #define LP5569_LEDTEST_DONE		0x80
    #define LP5569_RESET			0xFF
    #define LP5569_ADC_SHORTCIRC_LIM	80
    #define LP5569_EXT_CLK_USED		0x08
    #define LP5569_ENG_STATUS_MASK		0x07
    
    #define LP5569_FADER_MAPPING_MASK	0xC0
    #define LP5569_FADER_MAPPING_SHIFT	6
    
    /* Memory Page Selection */
    #define LP5569_PAGE_ENG1		0
    #define LP5569_PAGE_ENG2		1
    #define LP5569_PAGE_ENG3		2
    #define LP5569_PAGE_MUX1		3
    #define LP5569_PAGE_MUX2		4
    #define LP5569_PAGE_MUX3		5
    
    /* Program Memory Operations */
    #define LP5569_MODE_ENG1_M		0xC0	/* Operation Mode Register */
    #define LP5569_MODE_ENG2_M		0x30
    #define LP5569_MODE_ENG3_M		0x0C
    #define LP5569_LOAD_ENG1		0x40
    #define LP5569_LOAD_ENG2		0x10
    #define LP5569_LOAD_ENG3		0x04
    
    #define LP5569_ENG1_IS_LOADING(mode)	\
    	((mode & LP5569_MODE_ENG1_M) == LP5569_LOAD_ENG1)
    #define LP5569_ENG2_IS_LOADING(mode)	\
    	((mode & LP5569_MODE_ENG2_M) == LP5569_LOAD_ENG2)
    #define LP5569_ENG3_IS_LOADING(mode)	\
    	((mode & LP5569_MODE_ENG3_M) == LP5569_LOAD_ENG3)
    
    #define LP5569_EXEC_ENG1_M		0xC0	/* Enable Register */
    #define LP5569_EXEC_ENG2_M		0x30
    #define LP5569_EXEC_ENG3_M		0x0C
    #define LP5569_EXEC_M			0xFC
    #define LP5569_RUN_ENG1			0x80
    #define LP5569_RUN_ENG2			0x20
    #define LP5569_RUN_ENG3			0x08
    
    #define LED_ACTIVE(mux, led)		(!!(mux & (0x0001 << led)))
    
    enum lp5569_chip_id {
    	LP5569,
    };
    #if 0
    static int lp5569_init_program_engine(struct lp55xx_chip *chip);
    #endif
    static inline void lp5569_wait_opmode_done(void)
    {
    	usleep_range(1000, 2000);
    }
    
    static void lp5569_set_led_current(struct lp55xx_led *led, u8 led_current)
    {
    	led->led_current = led_current;
    	lp55xx_write(led->chip, LP5569_REG_LED_CURRENT_BASE + led->chan_nr,
    		led_current);
    }
    
    static int lp5569_post_init_device(struct lp55xx_chip *chip)
    {
    	int ret;
    
    	/* MISC.int_clk_en bit is STATIC and should only be changed when CONFIG.CHIP_EN = 0 */
    	/* Set internal clock here */
    	ret = lp55xx_write(chip, LP5569_REG_CONFIG, LP5569_INTERNAL_CLK);
    
    	/* then enable en_bit */
    	ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
    	if (ret)
    		return ret;
    
    	/* Chip startup time is 500 us, 1 - 2 ms gives some margin */
    	usleep_range(1000, 2000);
    
    	ret = lp55xx_write(chip, LP5569_REG_CONFIG,
    			    LP5569_AUTO_INC | LP5569_PWR_SAVE |
    			    LP5569_CP_AUTO | LP5569_INTERNAL_CLK |
    			    LP5569_PWM_PWR_SAVE);
    	if (ret)
    		return ret;
    
    #if 0
    	/* turn on all leds */
    	ret = lp55xx_write(chip, LP5569_REG_ENABLE_LEDS_MSB, 0x01);
    	if (ret)
    		return ret;
    
    	ret = lp55xx_write(chip, LP5569_REG_ENABLE_LEDS_LSB, 0xff);
    	if (ret)
    		return ret;
    	return lp5569_init_program_engine(chip);
    #else
    	return 0;
    #endif
    }
    
    static void lp5569_load_engine(struct lp55xx_chip *chip)
    {
    	enum lp55xx_engine_index idx = chip->engine_idx;
    	u8 mask[] = {
    		[LP55XX_ENGINE_1] = LP5569_MODE_ENG1_M,
    		[LP55XX_ENGINE_2] = LP5569_MODE_ENG2_M,
    		[LP55XX_ENGINE_3] = LP5569_MODE_ENG3_M,
    	};
    
    	u8 val[] = {
    		[LP55XX_ENGINE_1] = LP5569_LOAD_ENG1,
    		[LP55XX_ENGINE_2] = LP5569_LOAD_ENG2,
    		[LP55XX_ENGINE_3] = LP5569_LOAD_ENG3,
    	};
    
    	lp55xx_update_bits(chip, LP5569_REG_OP_MODE, mask[idx], val[idx]);
    
    	lp5569_wait_opmode_done();
    }
    
    static void lp5569_load_engine_and_select_page(struct lp55xx_chip *chip)
    {
    	enum lp55xx_engine_index idx = chip->engine_idx;
    	u8 page_sel[] = {
    		[LP55XX_ENGINE_1] = LP5569_PAGE_ENG1,
    		[LP55XX_ENGINE_2] = LP5569_PAGE_ENG2,
    		[LP55XX_ENGINE_3] = LP5569_PAGE_ENG3,
    	};
    
    	lp5569_load_engine(chip);
    
    	lp55xx_write(chip, LP5569_REG_PROG_PAGE_SEL, page_sel[idx]);
    }
    
    static void lp5569_stop_all_engines(struct lp55xx_chip *chip)
    {
    	lp55xx_write(chip, LP5569_REG_OP_MODE, 0);
    	lp5569_wait_opmode_done();
    	lp55xx_write(chip, LP5569_REG_OP_EXEC, 0);
    	lp5569_wait_opmode_done();
    }
    
    static void lp5569_stop_engine(struct lp55xx_chip *chip)
    {
    	enum lp55xx_engine_index idx = chip->engine_idx;
    	u8 mask[] = {
    		[LP55XX_ENGINE_1] = LP5569_MODE_ENG1_M,
    		[LP55XX_ENGINE_2] = LP5569_MODE_ENG2_M,
    		[LP55XX_ENGINE_3] = LP5569_MODE_ENG3_M,
    	};
    
    	lp55xx_update_bits(chip, LP5569_REG_OP_MODE, mask[idx], 0);
    	lp5569_wait_opmode_done();
    	lp55xx_update_bits(chip, LP5569_REG_OP_EXEC, mask[idx], 0);
    	lp5569_wait_opmode_done();
    }
    
    static void lp5569_turn_off_channels(struct lp55xx_chip *chip)
    {
    	int i;
    
    	for (i = 0; i < LP5569_MAX_LEDS; i++)
    		lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + i, 0);
    }
    
    static void lp5569_run_engine(struct lp55xx_chip *chip, bool start)
    {
    	int ret;
    	u8 mode;
    	u8 exec;
    
    	/* stop engine */
    	if (!start) {
    		lp5569_stop_engine(chip);
    		lp5569_turn_off_channels(chip);
    		return;
    	}
    
    	/*
    	 * To run the engine,
    	 * operation mode and enable register should updated at the same time
    	 */
    
    	ret = lp55xx_read(chip, LP5569_REG_OP_MODE, &mode);
    	if (ret)
    		return;
    
    	ret = lp55xx_read(chip, LP5569_REG_OP_EXEC, &exec);
    	if (ret)
    		return;
    
    	/* change operation mode to RUN only when each engine is loading */
    	if (LP5569_ENG1_IS_LOADING(mode)) {
    		mode = (mode & ~LP5569_MODE_ENG1_M) | LP5569_RUN_ENG1;
    		exec = (exec & ~LP5569_EXEC_ENG1_M) | LP5569_RUN_ENG1;
    	}
    
    	if (LP5569_ENG2_IS_LOADING(mode)) {
    		mode = (mode & ~LP5569_MODE_ENG2_M) | LP5569_RUN_ENG2;
    		exec = (exec & ~LP5569_EXEC_ENG2_M) | LP5569_RUN_ENG2;
    	}
    
    	if (LP5569_ENG3_IS_LOADING(mode)) {
    		mode = (mode & ~LP5569_MODE_ENG3_M) | LP5569_RUN_ENG3;
    		exec = (exec & ~LP5569_EXEC_ENG3_M) | LP5569_RUN_ENG3;
    	}
    	lp55xx_write(chip, LP5569_REG_OP_MODE, mode);
    	lp5569_wait_opmode_done();
    
    	lp55xx_write(chip, LP5569_REG_OP_EXEC, exec);
    }
    #if 0
    static int lp5569_init_program_engine(struct lp55xx_chip *chip)
    {
    	int i;
    	int j;
    	int ret;
    	u8 status;
    	/* one pattern per engine setting LED MUX start and stop addresses */
    	static const u8 pattern[][LP5569_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},
    	};
    
    	/* hardcode 32 bytes of memory for each engine from program memory */
    	ret = lp55xx_write(chip, LP5569_REG_CH1_PROG_START, 0x00);
    	if (ret)
    		return ret;
    
    	ret = lp55xx_write(chip, LP5569_REG_CH2_PROG_START, 0x10);
    	if (ret)
    		return ret;
    
    	ret = lp55xx_write(chip, LP5569_REG_CH3_PROG_START, 0x20);
    	if (ret)
    		return ret;
    
    	/* write LED MUX address space for each engine */
    	for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
    		chip->engine_idx = i;
    		lp5569_load_engine_and_select_page(chip);
    
    		for (j = 0; j < LP5569_PROGRAM_LENGTH; j++) {
    			ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + j,
    					pattern[i - 1][j]);
    			if (ret)
    				goto out;
    		}
    	}
    
    	lp5569_run_engine(chip, true);
    
    	/* Let the programs run for couple of ms and check the engine status */
    	usleep_range(3000, 6000);
    	lp55xx_read(chip, LP5569_REG_STATUS, &status);
    	status &= LP5569_ENG_STATUS_MASK;
    
    	if (status != LP5569_ENG_STATUS_MASK) {
    		dev_err(&chip->cl->dev,
    			"could not configure LED engine, status = 0x%.2x\n",
    			status);
    		ret = -1;
    	}
    
    out:
    	lp5569_stop_all_engines(chip);
    	return ret;
    }
    #endif
    static int lp5569_update_program_memory(struct lp55xx_chip *chip,
    					const u8 *data, size_t size)
    {
    	u8 pattern[LP5569_PROGRAM_LENGTH] = {0};
    	unsigned cmd;
    	char c[3];
    	int nrchars;
    	int ret;
    	int offset = 0;
    	int i = 0;
    
    	while ((offset < size - 1) && (i < LP5569_PROGRAM_LENGTH)) {
    		/* separate sscanfs because length is working only for %s */
    		ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
    		if (ret != 1)
    			goto err;
    
    		ret = sscanf(c, "%2x", &cmd);
    		if (ret != 1)
    			goto err;
    
    		pattern[i] = (u8)cmd;
    		offset += nrchars;
    		i++;
    	}
    
    	/* Each instruction is 16bit long. Check that length is even */
    	if (i % 2)
    		goto err;
    
    	for (i = 0; i < LP5569_PROGRAM_LENGTH; i++) {
    		ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + i, pattern[i]);
    		if (ret)
    			return -EINVAL;
    	}
    
    	return size;
    
    err:
    	dev_err(&chip->cl->dev, "wrong pattern format\n");
    	return -EINVAL;
    }
    
    static void lp5569_firmware_loaded(struct lp55xx_chip *chip)
    {
    	const struct firmware *fw = chip->fw;
    
    	if (fw->size > LP5569_PROGRAM_LENGTH) {
    		dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
    			fw->size);
    		return;
    	}
    
    	/*
    	 * Program momery sequence
    	 *  1) set engine mode to "LOAD"
    	 *  2) write firmware data into program memory
    	 */
    
    	lp5569_load_engine_and_select_page(chip);
    	lp5569_update_program_memory(chip, fw->data, fw->size);
    }
    
    static ssize_t show_engine_mode(struct device *dev,
    				struct device_attribute *attr,
    				char *buf, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
    
    	switch (mode) {
    	case LP55XX_ENGINE_RUN:
    		return sprintf(buf, "run\n");
    	case LP55XX_ENGINE_LOAD:
    		return sprintf(buf, "load\n");
    	case LP55XX_ENGINE_DISABLED:
    	default:
    		return sprintf(buf, "disabled\n");
    	}
    }
    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 lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	struct lp55xx_engine *engine = &chip->engines[nr - 1];
    
    	mutex_lock(&chip->lock);
    
    	chip->engine_idx = nr;
    
    	if (!strncmp(buf, "run", 3)) {
    		lp5569_run_engine(chip, true);
    		engine->mode = LP55XX_ENGINE_RUN;
    	} else if (!strncmp(buf, "load", 4)) {
    		lp5569_stop_engine(chip);
    		lp5569_load_engine(chip);
    		engine->mode = LP55XX_ENGINE_LOAD;
    	} else if (!strncmp(buf, "disabled", 8)) {
    		lp5569_stop_engine(chip);
    		engine->mode = LP55XX_ENGINE_DISABLED;
    	}
    
    	mutex_unlock(&chip->lock);
    
    	return len;
    }
    store_mode(1)
    store_mode(2)
    store_mode(3)
    
    static int lp5569_mux_parse(const char *buf, u16 *mux, size_t len)
    {
    	u16 tmp_mux = 0;
    	int i;
    
    	len = min_t(int, len, LP5569_MAX_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;
    		}
    	}
    	*mux = tmp_mux;
    
    	return 0;
    }
    
    static void lp5569_mux_to_array(u16 led_mux, char *array)
    {
    	int i, pos = 0;
    	for (i = 0; i < LP5569_MAX_LEDS; i++)
    		pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
    
    	array[pos] = '\0';
    }
    
    static ssize_t show_engine_leds(struct device *dev,
    			    struct device_attribute *attr,
    			    char *buf, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	char mux[LP5569_MAX_LEDS + 1];
    
    	lp5569_mux_to_array(chip->engines[nr - 1].led_mux, mux);
    
    	return sprintf(buf, "%s\n", mux);
    }
    show_leds(1)
    show_leds(2)
    show_leds(3)
    
    static int lp5569_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
    {
    	struct lp55xx_engine *engine = &chip->engines[nr - 1];
    	int ret;
    	u8 mux_page[] = {
    		[LP55XX_ENGINE_1] = LP5569_PAGE_MUX1,
    		[LP55XX_ENGINE_2] = LP5569_PAGE_MUX2,
    		[LP55XX_ENGINE_3] = LP5569_PAGE_MUX3,
    	};
    
    	lp5569_load_engine(chip);
    
    	ret = lp55xx_write(chip, LP5569_REG_PROG_PAGE_SEL, mux_page[nr]);
    	if (ret)
    		return ret;
    
    	ret = lp55xx_write(chip, LP5569_REG_PROG_MEM , (u8)(mux >> 8));
    	if (ret)
    		return ret;
    
    	ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + 1, (u8)(mux));
    	if (ret)
    		return ret;
    
    	engine->led_mux = mux;
    	return 0;
    }
    
    static ssize_t store_engine_leds(struct device *dev,
    			     struct device_attribute *attr,
    			     const char *buf, size_t len, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	struct lp55xx_engine *engine = &chip->engines[nr - 1];
    	u16 mux = 0;
    	ssize_t ret;
    
    	if (lp5569_mux_parse(buf, &mux, len))
    		return -EINVAL;
    
    	mutex_lock(&chip->lock);
    
    	chip->engine_idx = nr;
    	ret = -EINVAL;
    
    	if (engine->mode != LP55XX_ENGINE_LOAD)
    		goto leave;
    
    	if (lp5569_load_mux(chip, mux, nr))
    		goto leave;
    
    	ret = len;
    leave:
    	mutex_unlock(&chip->lock);
    	return ret;
    }
    store_leds(1)
    store_leds(2)
    store_leds(3)
    
    static ssize_t store_engine_load(struct device *dev,
    			     struct device_attribute *attr,
    			     const char *buf, size_t len, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	int ret;
    
    	mutex_lock(&chip->lock);
    
    	chip->engine_idx = nr;
    	lp5569_load_engine_and_select_page(chip);
    	ret = lp5569_update_program_memory(chip, buf, len);
    
    	mutex_unlock(&chip->lock);
    
    	return ret;
    }
    store_load(1)
    store_load(2)
    store_load(3)
    
    static ssize_t lp5569_selftest(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;
    	struct lp55xx_platform_data *pdata = chip->pdata;
    	int i, ret, pos = 0;
    	u8 status, adc, vdd;
    
    	mutex_lock(&chip->lock);
    
    	ret = lp55xx_read(chip, LP5569_REG_STATUS, &status);
    	if (ret < 0)
    		goto fail;
    
    	/* Check that ext clock is really in use if requested */
    	if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
    		if  ((status & LP5569_EXT_CLK_USED) == 0)
    			goto fail;
    	}
    
    	/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
    	lp55xx_write(chip, LP5569_REG_LED_TEST_CTRL, LP5569_EN_LEDTEST | 16);
    	usleep_range(3000, 6000); /* ADC conversion time is typically 2.7 ms */
    	ret = lp55xx_read(chip, LP5569_REG_STATUS, &status);
    	if (ret < 0)
    		goto fail;
    
    	if (!(status & LP5569_LEDTEST_DONE))
    		usleep_range(3000, 6000); /* Was not ready. Wait little bit */
    
    	ret = lp55xx_read(chip, LP5569_REG_LED_TEST_ADC, &vdd);
    	if (ret < 0)
    		goto fail;
    
    	vdd--;	/* There may be some fluctuation in measurement */
    
    	for (i = 0; i < LP5569_MAX_LEDS; i++) {
    		/* Skip non-existing channels */
    		if (pdata->led_config[i].led_current == 0)
    			continue;
    
    		/* Set default current */
    		lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + i,
    			pdata->led_config[i].led_current);
    
    		lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + i, 0xff);
    		/* let current stabilize 2 - 4ms before measurements start */
    		usleep_range(2000, 4000);
    		lp55xx_write(chip, LP5569_REG_LED_TEST_CTRL,
    			     LP5569_EN_LEDTEST | i);
    		/* ADC conversion time is 2.7 ms typically */
    		usleep_range(3000, 6000);
    		ret = lp55xx_read(chip, LP5569_REG_STATUS, &status);
    		if (ret < 0)
    			goto fail;
    
    		if (!(status & LP5569_LEDTEST_DONE))
    			usleep_range(3000, 6000);/* Was not ready. Wait. */
    
    		ret = lp55xx_read(chip, LP5569_REG_LED_TEST_ADC, &adc);
    		if (ret < 0)
    			goto fail;
    
    		if (adc >= vdd || adc < LP5569_ADC_SHORTCIRC_LIM)
    			pos += sprintf(buf + pos, "LED %d FAIL\n", i);
    
    		lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + i, 0x00);
    
    		/* Restore current */
    		lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + i,
    			led->led_current);
    		led++;
    	}
    	if (pos == 0)
    		pos = sprintf(buf, "OK\n");
    	goto release_lock;
    fail:
    	pos = sprintf(buf, "FAIL\n");
    
    release_lock:
    	mutex_unlock(&chip->lock);
    
    	return pos;
    }
    
    #define show_fader(nr)						\
    static ssize_t show_master_fader##nr(struct device *dev,	\
    			    struct device_attribute *attr,	\
    			    char *buf)				\
    {								\
    	return show_master_fader(dev, attr, buf, nr);		\
    }
    
    #define store_fader(nr)						\
    static ssize_t store_master_fader##nr(struct device *dev,	\
    			     struct device_attribute *attr,	\
    			     const char *buf, size_t len)	\
    {								\
    	return store_master_fader(dev, attr, buf, len, nr);	\
    }
    
    static ssize_t show_master_fader(struct device *dev,
    				 struct device_attribute *attr,
    				 char *buf, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	int ret;
    	u8 val;
    
    	mutex_lock(&chip->lock);
    	ret = lp55xx_read(chip, LP5569_REG_MASTER_FADER_BASE + nr - 1, &val);
    	mutex_unlock(&chip->lock);
    
    	if (ret == 0)
    		ret = sprintf(buf, "%u\n", val);
    
    	return ret;
    }
    show_fader(1)
    show_fader(2)
    show_fader(3)
    
    static ssize_t store_master_fader(struct device *dev,
    				  struct device_attribute *attr,
    				  const char *buf, size_t len, int nr)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
    	struct lp55xx_chip *chip = led->chip;
    	int ret;
    	unsigned long val;
    
    	if (kstrtoul(buf, 0, &val))
    		return -EINVAL;
    
    	if (val > 0xff)
    		return -EINVAL;
    
    	mutex_lock(&chip->lock);
    	ret = lp55xx_write(chip, LP5569_REG_MASTER_FADER_BASE + nr - 1,
    			   (u8)val);
    	mutex_unlock(&chip->lock);
    
    	if (ret == 0)
    		ret = len;
    
    	return ret;
    }
    store_fader(1)
    store_fader(2)
    store_fader(3)
    
    static ssize_t show_master_fader_leds(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;
    	int i, ret, pos = 0;
    	u8 val;
    
    	mutex_lock(&chip->lock);
    
    	for (i = 0; i < LP5569_MAX_LEDS; i++) {
    		ret = lp55xx_read(chip, LP5569_REG_LED_CTRL_BASE + i, &val);
    		if (ret)
    			goto leave;
    
    		val = (val & LP5569_FADER_MAPPING_MASK)
    			>> LP5569_FADER_MAPPING_SHIFT;
    		if (val > 3) {
    			ret = -EINVAL;
    			goto leave;
    		}
    		buf[pos++] = val + '0';
    	}
    	buf[pos++] = '\n';
    	ret = pos;
    leave:
    	mutex_unlock(&chip->lock);
    	return ret;
    }
    
    static ssize_t store_master_fader_leds(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;
    	int i, n, ret;
    	u8 val;
    
    	n = min_t(int, len, LP5569_MAX_LEDS);
    
    	mutex_lock(&chip->lock);
    
    	for (i = 0; i < n; i++) {
    		if (buf[i] >= '0' && buf[i] <= '3') {
    			val = (buf[i] - '0') << LP5569_FADER_MAPPING_SHIFT;
    			ret = lp55xx_update_bits(chip,
    						 LP5569_REG_LED_CTRL_BASE + i,
    						 LP5569_FADER_MAPPING_MASK,
    						 val);
    			if (ret)
    				goto leave;
    		} else {
    			ret = -EINVAL;
    			goto leave;
    		}
    	}
    	ret = len;
    leave:
    	mutex_unlock(&chip->lock);
    	return ret;
    }
    
    static int lp5569_led_brightness(struct lp55xx_led *led)
    {
    	struct lp55xx_chip *chip = led->chip;
    	int ret;
    
    	mutex_lock(&chip->lock);
    	ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led->chan_nr,
    		     led->brightness);
    	mutex_unlock(&chip->lock);
    	return ret;
    }
    
    static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
    static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
    static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
    static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds);
    static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds);
    static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds);
    static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
    static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
    static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
    static LP55XX_DEV_ATTR_RO(selftest, lp5569_selftest);
    static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
    			  store_master_fader1);
    static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
    			  store_master_fader2);
    static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
    			  store_master_fader3);
    static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
    			  store_master_fader_leds);
    
    static struct attribute *lp5569_attributes[] = {
    	&dev_attr_engine1_mode.attr,
    	&dev_attr_engine2_mode.attr,
    	&dev_attr_engine3_mode.attr,
    	&dev_attr_engine1_load.attr,
    	&dev_attr_engine2_load.attr,
    	&dev_attr_engine3_load.attr,
    	&dev_attr_engine1_leds.attr,
    	&dev_attr_engine2_leds.attr,
    	&dev_attr_engine3_leds.attr,
    	&dev_attr_selftest.attr,
    	&dev_attr_master_fader1.attr,
    	&dev_attr_master_fader2.attr,
    	&dev_attr_master_fader3.attr,
    	&dev_attr_master_fader_leds.attr,
    	NULL,
    };
    
    static const struct attribute_group lp5569_group = {
    	.attrs = lp5569_attributes,
    };
    
    /* Chip specific configurations */
    static struct lp55xx_device_config lp5569_cfg = {
    	.reset = {
    		.addr = LP5569_REG_RESET,
    		.val  = LP5569_RESET,
    	},
    	.enable = {
    		.addr = LP5569_REG_ENABLE,
    		.val  = LP5569_ENABLE,
    	},
    	.max_channel  = LP5569_MAX_LEDS,
    	.post_init_device   = lp5569_post_init_device,
    	.brightness_fn      = lp5569_led_brightness,
    	.set_led_current    = lp5569_set_led_current,
    	.firmware_cb        = lp5569_firmware_loaded,
    	.run_engine         = lp5569_run_engine,
    	.dev_attr_group     = &lp5569_group,
    };
    
    static int lp5569_probe(struct i2c_client *client,
    			const struct i2c_device_id *id)
    {
    	int ret;
    	struct lp55xx_chip *chip;
    	struct lp55xx_led *led;
    	struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
    	struct device_node *np = client->dev.of_node;
    
    	if (!pdata) {
    		if (np) {
    			pdata = lp55xx_of_populate_pdata(&client->dev, np);
    			if (IS_ERR(pdata))
    				return PTR_ERR(pdata);
    		} else {
    			dev_err(&client->dev, "no platform data\n");
    			return -EINVAL;
    		}
    	}
    
    	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 = &lp5569_cfg;
    
    	mutex_init(&chip->lock);
    
    	i2c_set_clientdata(client, led);
    
    	ret = lp55xx_init_device(chip);
    	if (ret)
    		goto err_init;
    
    	dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
    
    	ret = lp55xx_register_leds(led, chip);
    	if (ret)
    		goto err_register_leds;
    
    	ret = lp55xx_register_sysfs(chip);
    	if (ret) {
    		dev_err(&client->dev, "registering sysfs failed\n");
    		goto err_register_sysfs;
    	}
    
    	return 0;
    
    err_register_sysfs:
    	lp55xx_unregister_leds(led, chip);
    err_register_leds:
    	lp55xx_deinit_device(chip);
    err_init:
    	return ret;
    }
    
    static int lp5569_remove(struct i2c_client *client)
    {
    	struct lp55xx_led *led = i2c_get_clientdata(client);
    	struct lp55xx_chip *chip = led->chip;
    
    	lp5569_stop_all_engines(chip);
    	lp55xx_unregister_sysfs(chip);
    	lp55xx_unregister_leds(led, chip);
    	lp55xx_deinit_device(chip);
    
    	return 0;
    }
    
    static const struct i2c_device_id lp5569_id[] = {
    	{ "lp5569",  LP5569 },
    	{ }
    };
    
    MODULE_DEVICE_TABLE(i2c, lp5569_id);
    
    #ifdef CONFIG_OF
    static const struct of_device_id of_lp5569_leds_match[] = {
    	{ .compatible = "national,lp5569", },
    	{ .compatible = "ti,lp5569", },
    	{},
    };
    
    MODULE_DEVICE_TABLE(of, of_lp5569_leds_match);
    #endif
    
    static struct i2c_driver lp5569_driver = {
    	.driver = {
    		.name	= "lp5569x",
    		.of_match_table = of_match_ptr(of_lp5569_leds_match),
    	},
    	.probe		= lp5569_probe,
    	.remove		= lp5569_remove,
    	.id_table	= lp5569_id,
    };
    
    module_i2c_driver(lp5569_driver);
    
    MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
    MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
    MODULE_AUTHOR("Woonyong Yun <wyyun@humaxdigital.com>");
    MODULE_DESCRIPTION("LP5569 LED engine");
    MODULE_LICENSE("GPL");

  • Hi Daniel,

    How do I put it in front of the whole program?
    Your code in already include this part. 'red: dw 0000000100000000b grn: dw 0000000000010000b blu: dw 0000000000000001b' is translate to '01 00 00 10 00 01' by LASM assembler assembler

    If I am programming multiple engines, I upload each engine's portion separately. 
    How you separate the hex program? The whole hex program only need to be upload one time. It also should be OK if you upload it in 3 times.

    What address do I upload the whole program to?
    The program address is from register 50h to 6Fh,while page selected by register 4Fh. You can find it in datasheet page 39. Sorry that I am not a software engineer, it is really hard for me to understand the source code in short time. I did not find the declaration of echo "xxxx..." > enginex_load command. Here is also head files 'leds-lp55xx-common.h' and 'leds-lp55xx.h' may also need to be shared to me.

    One thing occur to me that may cause your situation is that, here is prog_start_addr registers (0x4B, 0x4C, 0x4D) that set the program start address of each engine. Do you set it in your program? (for your program, write 0x03 to register 0x4B for engine1, write 0x09 to register 0x4C for engine2, write 0x10 to register 0x4D for engine3)

  • Hi Hardy, 

    Thank you for trying to help, but I think I need help from someone with more software background. The source files you are asking come as part of the linux kernel which you can find. 

    Regards,

    Daniel

  • HI Daniel,

    Sorry that there is no other one in our team have Linux experience... Since your engine code is good, I think we are almost closed and just some register setting need to check.

    Have you tried what I mentioned in former reply about the prog_start_addr registers? What the register 0x4B, 0x4C, 0x4D values in your original setting? I think there should be single register read function in the code.