xref: /openbmc/linux/drivers/leds/rgb/leds-qcom-lpg.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
124e2d05dSBjorn Andersson // SPDX-License-Identifier: GPL-2.0-only
224e2d05dSBjorn Andersson /*
324e2d05dSBjorn Andersson  * Copyright (c) 2017-2022 Linaro Ltd
424e2d05dSBjorn Andersson  * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
5b00d2ed3SAnjelique Melendez  * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
624e2d05dSBjorn Andersson  */
724e2d05dSBjorn Andersson #include <linux/bits.h>
824e2d05dSBjorn Andersson #include <linux/bitfield.h>
924e2d05dSBjorn Andersson #include <linux/led-class-multicolor.h>
1024e2d05dSBjorn Andersson #include <linux/module.h>
1124e2d05dSBjorn Andersson #include <linux/of.h>
1224e2d05dSBjorn Andersson #include <linux/platform_device.h>
1324e2d05dSBjorn Andersson #include <linux/pwm.h>
1424e2d05dSBjorn Andersson #include <linux/regmap.h>
1524e2d05dSBjorn Andersson #include <linux/slab.h>
1624e2d05dSBjorn Andersson 
1724e2d05dSBjorn Andersson #define LPG_SUBTYPE_REG		0x05
1824e2d05dSBjorn Andersson #define  LPG_SUBTYPE_LPG	0x2
1924e2d05dSBjorn Andersson #define  LPG_SUBTYPE_PWM	0xb
20b00d2ed3SAnjelique Melendez #define  LPG_SUBTYPE_HI_RES_PWM	0xc
2124e2d05dSBjorn Andersson #define  LPG_SUBTYPE_LPG_LITE	0x11
2224e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_REG	0x40
2324e2d05dSBjorn Andersson #define LPG_SIZE_CLK_REG	0x41
2424e2d05dSBjorn Andersson #define  PWM_CLK_SELECT_MASK	GENMASK(1, 0)
25b00d2ed3SAnjelique Melendez #define  PWM_CLK_SELECT_HI_RES_MASK	GENMASK(2, 0)
26b00d2ed3SAnjelique Melendez #define  PWM_SIZE_HI_RES_MASK	GENMASK(6, 4)
2724e2d05dSBjorn Andersson #define LPG_PREDIV_CLK_REG	0x42
2824e2d05dSBjorn Andersson #define  PWM_FREQ_PRE_DIV_MASK	GENMASK(6, 5)
2924e2d05dSBjorn Andersson #define  PWM_FREQ_EXP_MASK	GENMASK(2, 0)
3024e2d05dSBjorn Andersson #define PWM_TYPE_CONFIG_REG	0x43
3124e2d05dSBjorn Andersson #define PWM_VALUE_REG		0x44
3224e2d05dSBjorn Andersson #define PWM_ENABLE_CONTROL_REG	0x46
3324e2d05dSBjorn Andersson #define PWM_SYNC_REG		0x47
3424e2d05dSBjorn Andersson #define LPG_RAMP_DURATION_REG	0x50
3524e2d05dSBjorn Andersson #define LPG_HI_PAUSE_REG	0x52
3624e2d05dSBjorn Andersson #define LPG_LO_PAUSE_REG	0x54
3724e2d05dSBjorn Andersson #define LPG_HI_IDX_REG		0x56
3824e2d05dSBjorn Andersson #define LPG_LO_IDX_REG		0x57
3924e2d05dSBjorn Andersson #define PWM_SEC_ACCESS_REG	0xd0
4024e2d05dSBjorn Andersson #define PWM_DTEST_REG(x)	(0xe2 + (x) - 1)
4124e2d05dSBjorn Andersson 
4224e2d05dSBjorn Andersson #define TRI_LED_SRC_SEL		0x45
4324e2d05dSBjorn Andersson #define TRI_LED_EN_CTL		0x46
4424e2d05dSBjorn Andersson #define TRI_LED_ATC_CTL		0x47
4524e2d05dSBjorn Andersson 
4624e2d05dSBjorn Andersson #define LPG_LUT_REG(x)		(0x40 + (x) * 2)
4724e2d05dSBjorn Andersson #define RAMP_CONTROL_REG	0xc8
4824e2d05dSBjorn Andersson 
49b00d2ed3SAnjelique Melendez #define LPG_RESOLUTION_9BIT	BIT(9)
50b00d2ed3SAnjelique Melendez #define LPG_RESOLUTION_15BIT	BIT(15)
5124e2d05dSBjorn Andersson #define LPG_MAX_M		7
52b00d2ed3SAnjelique Melendez #define LPG_MAX_PREDIV		6
5324e2d05dSBjorn Andersson 
5424e2d05dSBjorn Andersson struct lpg_channel;
5524e2d05dSBjorn Andersson struct lpg_data;
5624e2d05dSBjorn Andersson 
5724e2d05dSBjorn Andersson /**
5824e2d05dSBjorn Andersson  * struct lpg - LPG device context
5924e2d05dSBjorn Andersson  * @dev:	pointer to LPG device
6024e2d05dSBjorn Andersson  * @map:	regmap for register access
6124e2d05dSBjorn Andersson  * @lock:	used to synchronize LED and pwm callback requests
6224e2d05dSBjorn Andersson  * @pwm:	PWM-chip object, if operating in PWM mode
6324e2d05dSBjorn Andersson  * @data:	reference to version specific data
6424e2d05dSBjorn Andersson  * @lut_base:	base address of the LUT block (optional)
6524e2d05dSBjorn Andersson  * @lut_size:	number of entries in the LUT block
6624e2d05dSBjorn Andersson  * @lut_bitmap:	allocation bitmap for LUT entries
6724e2d05dSBjorn Andersson  * @triled_base: base address of the TRILED block (optional)
6824e2d05dSBjorn Andersson  * @triled_src:	power-source for the TRILED
6924e2d05dSBjorn Andersson  * @triled_has_atc_ctl:	true if there is TRI_LED_ATC_CTL register
7024e2d05dSBjorn Andersson  * @triled_has_src_sel:	true if there is TRI_LED_SRC_SEL register
7124e2d05dSBjorn Andersson  * @channels:	list of PWM channels
7224e2d05dSBjorn Andersson  * @num_channels: number of @channels
7324e2d05dSBjorn Andersson  */
7424e2d05dSBjorn Andersson struct lpg {
7524e2d05dSBjorn Andersson 	struct device *dev;
7624e2d05dSBjorn Andersson 	struct regmap *map;
7724e2d05dSBjorn Andersson 
7824e2d05dSBjorn Andersson 	struct mutex lock;
7924e2d05dSBjorn Andersson 
8024e2d05dSBjorn Andersson 	struct pwm_chip pwm;
8124e2d05dSBjorn Andersson 
8224e2d05dSBjorn Andersson 	const struct lpg_data *data;
8324e2d05dSBjorn Andersson 
8424e2d05dSBjorn Andersson 	u32 lut_base;
8524e2d05dSBjorn Andersson 	u32 lut_size;
8624e2d05dSBjorn Andersson 	unsigned long *lut_bitmap;
8724e2d05dSBjorn Andersson 
8824e2d05dSBjorn Andersson 	u32 triled_base;
8924e2d05dSBjorn Andersson 	u32 triled_src;
9024e2d05dSBjorn Andersson 	bool triled_has_atc_ctl;
9124e2d05dSBjorn Andersson 	bool triled_has_src_sel;
9224e2d05dSBjorn Andersson 
9324e2d05dSBjorn Andersson 	struct lpg_channel *channels;
9424e2d05dSBjorn Andersson 	unsigned int num_channels;
9524e2d05dSBjorn Andersson };
9624e2d05dSBjorn Andersson 
9724e2d05dSBjorn Andersson /**
9824e2d05dSBjorn Andersson  * struct lpg_channel - per channel data
9924e2d05dSBjorn Andersson  * @lpg:	reference to parent lpg
10024e2d05dSBjorn Andersson  * @base:	base address of the PWM channel
10124e2d05dSBjorn Andersson  * @triled_mask: mask in TRILED to enable this channel
10224e2d05dSBjorn Andersson  * @lut_mask:	mask in LUT to start pattern generator for this channel
10324e2d05dSBjorn Andersson  * @subtype:	PMIC hardware block subtype
10424e2d05dSBjorn Andersson  * @in_use:	channel is exposed to LED framework
10524e2d05dSBjorn Andersson  * @color:	color of the LED attached to this channel
10624e2d05dSBjorn Andersson  * @dtest_line:	DTEST line for output, or 0 if disabled
10724e2d05dSBjorn Andersson  * @dtest_value: DTEST line configuration
10824e2d05dSBjorn Andersson  * @pwm_value:	duty (in microseconds) of the generated pulses, overridden by LUT
10924e2d05dSBjorn Andersson  * @enabled:	output enabled?
11024e2d05dSBjorn Andersson  * @period:	period (in nanoseconds) of the generated pulses
11124e2d05dSBjorn Andersson  * @clk_sel:	reference clock frequency selector
11224e2d05dSBjorn Andersson  * @pre_div_sel: divider selector of the reference clock
11324e2d05dSBjorn Andersson  * @pre_div_exp: exponential divider of the reference clock
114b00d2ed3SAnjelique Melendez  * @pwm_resolution_sel:	pwm resolution selector
11524e2d05dSBjorn Andersson  * @ramp_enabled: duty cycle is driven by iterating over lookup table
11624e2d05dSBjorn Andersson  * @ramp_ping_pong: reverse through pattern, rather than wrapping to start
11724e2d05dSBjorn Andersson  * @ramp_oneshot: perform only a single pass over the pattern
11824e2d05dSBjorn Andersson  * @ramp_reverse: iterate over pattern backwards
11924e2d05dSBjorn Andersson  * @ramp_tick_ms: length (in milliseconds) of one step in the pattern
12024e2d05dSBjorn Andersson  * @ramp_lo_pause_ms: pause (in milliseconds) before iterating over pattern
12124e2d05dSBjorn Andersson  * @ramp_hi_pause_ms: pause (in milliseconds) after iterating over pattern
12224e2d05dSBjorn Andersson  * @pattern_lo_idx: start index of associated pattern
12324e2d05dSBjorn Andersson  * @pattern_hi_idx: last index of associated pattern
12424e2d05dSBjorn Andersson  */
12524e2d05dSBjorn Andersson struct lpg_channel {
12624e2d05dSBjorn Andersson 	struct lpg *lpg;
12724e2d05dSBjorn Andersson 
12824e2d05dSBjorn Andersson 	u32 base;
12924e2d05dSBjorn Andersson 	unsigned int triled_mask;
13024e2d05dSBjorn Andersson 	unsigned int lut_mask;
13124e2d05dSBjorn Andersson 	unsigned int subtype;
13224e2d05dSBjorn Andersson 
13324e2d05dSBjorn Andersson 	bool in_use;
13424e2d05dSBjorn Andersson 
13524e2d05dSBjorn Andersson 	int color;
13624e2d05dSBjorn Andersson 
13724e2d05dSBjorn Andersson 	u32 dtest_line;
13824e2d05dSBjorn Andersson 	u32 dtest_value;
13924e2d05dSBjorn Andersson 
14024e2d05dSBjorn Andersson 	u16 pwm_value;
14124e2d05dSBjorn Andersson 	bool enabled;
14224e2d05dSBjorn Andersson 
14324e2d05dSBjorn Andersson 	u64 period;
14424e2d05dSBjorn Andersson 	unsigned int clk_sel;
14524e2d05dSBjorn Andersson 	unsigned int pre_div_sel;
14624e2d05dSBjorn Andersson 	unsigned int pre_div_exp;
147b00d2ed3SAnjelique Melendez 	unsigned int pwm_resolution_sel;
14824e2d05dSBjorn Andersson 
14924e2d05dSBjorn Andersson 	bool ramp_enabled;
15024e2d05dSBjorn Andersson 	bool ramp_ping_pong;
15124e2d05dSBjorn Andersson 	bool ramp_oneshot;
15224e2d05dSBjorn Andersson 	bool ramp_reverse;
15324e2d05dSBjorn Andersson 	unsigned short ramp_tick_ms;
15424e2d05dSBjorn Andersson 	unsigned long ramp_lo_pause_ms;
15524e2d05dSBjorn Andersson 	unsigned long ramp_hi_pause_ms;
15624e2d05dSBjorn Andersson 
15724e2d05dSBjorn Andersson 	unsigned int pattern_lo_idx;
15824e2d05dSBjorn Andersson 	unsigned int pattern_hi_idx;
15924e2d05dSBjorn Andersson };
16024e2d05dSBjorn Andersson 
16124e2d05dSBjorn Andersson /**
16224e2d05dSBjorn Andersson  * struct lpg_led - logical LED object
16324e2d05dSBjorn Andersson  * @lpg:		lpg context reference
16424e2d05dSBjorn Andersson  * @cdev:		LED class device
16524e2d05dSBjorn Andersson  * @mcdev:		Multicolor LED class device
16624e2d05dSBjorn Andersson  * @num_channels:	number of @channels
16724e2d05dSBjorn Andersson  * @channels:		list of channels associated with the LED
16824e2d05dSBjorn Andersson  */
16924e2d05dSBjorn Andersson struct lpg_led {
17024e2d05dSBjorn Andersson 	struct lpg *lpg;
17124e2d05dSBjorn Andersson 
17224e2d05dSBjorn Andersson 	struct led_classdev cdev;
17324e2d05dSBjorn Andersson 	struct led_classdev_mc mcdev;
17424e2d05dSBjorn Andersson 
17524e2d05dSBjorn Andersson 	unsigned int num_channels;
17624e2d05dSBjorn Andersson 	struct lpg_channel *channels[];
17724e2d05dSBjorn Andersson };
17824e2d05dSBjorn Andersson 
17924e2d05dSBjorn Andersson /**
18024e2d05dSBjorn Andersson  * struct lpg_channel_data - per channel initialization data
18124e2d05dSBjorn Andersson  * @base:		base address for PWM channel registers
18224e2d05dSBjorn Andersson  * @triled_mask:	bitmask for controlling this channel in TRILED
18324e2d05dSBjorn Andersson  */
18424e2d05dSBjorn Andersson struct lpg_channel_data {
18524e2d05dSBjorn Andersson 	unsigned int base;
18624e2d05dSBjorn Andersson 	u8 triled_mask;
18724e2d05dSBjorn Andersson };
18824e2d05dSBjorn Andersson 
18924e2d05dSBjorn Andersson /**
19024e2d05dSBjorn Andersson  * struct lpg_data - initialization data
19124e2d05dSBjorn Andersson  * @lut_base:		base address of LUT block
19224e2d05dSBjorn Andersson  * @lut_size:		number of entries in LUT
19324e2d05dSBjorn Andersson  * @triled_base:	base address of TRILED
19424e2d05dSBjorn Andersson  * @triled_has_atc_ctl:	true if there is TRI_LED_ATC_CTL register
19524e2d05dSBjorn Andersson  * @triled_has_src_sel:	true if there is TRI_LED_SRC_SEL register
19624e2d05dSBjorn Andersson  * @num_channels:	number of channels in LPG
19724e2d05dSBjorn Andersson  * @channels:		list of channel initialization data
19824e2d05dSBjorn Andersson  */
19924e2d05dSBjorn Andersson struct lpg_data {
20024e2d05dSBjorn Andersson 	unsigned int lut_base;
20124e2d05dSBjorn Andersson 	unsigned int lut_size;
20224e2d05dSBjorn Andersson 	unsigned int triled_base;
20324e2d05dSBjorn Andersson 	bool triled_has_atc_ctl;
20424e2d05dSBjorn Andersson 	bool triled_has_src_sel;
20524e2d05dSBjorn Andersson 	int num_channels;
20624e2d05dSBjorn Andersson 	const struct lpg_channel_data *channels;
20724e2d05dSBjorn Andersson };
20824e2d05dSBjorn Andersson 
triled_set(struct lpg * lpg,unsigned int mask,unsigned int enable)20924e2d05dSBjorn Andersson static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable)
21024e2d05dSBjorn Andersson {
21124e2d05dSBjorn Andersson 	/* Skip if we don't have a triled block */
21224e2d05dSBjorn Andersson 	if (!lpg->triled_base)
21324e2d05dSBjorn Andersson 		return 0;
21424e2d05dSBjorn Andersson 
21524e2d05dSBjorn Andersson 	return regmap_update_bits(lpg->map, lpg->triled_base + TRI_LED_EN_CTL,
21624e2d05dSBjorn Andersson 				  mask, enable);
21724e2d05dSBjorn Andersson }
21824e2d05dSBjorn Andersson 
lpg_lut_store(struct lpg * lpg,struct led_pattern * pattern,size_t len,unsigned int * lo_idx,unsigned int * hi_idx)21924e2d05dSBjorn Andersson static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
22024e2d05dSBjorn Andersson 			 size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
22124e2d05dSBjorn Andersson {
22224e2d05dSBjorn Andersson 	unsigned int idx;
22324e2d05dSBjorn Andersson 	u16 val;
22424e2d05dSBjorn Andersson 	int i;
22524e2d05dSBjorn Andersson 
22624e2d05dSBjorn Andersson 	idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size,
22724e2d05dSBjorn Andersson 					 0, len, 0);
22824e2d05dSBjorn Andersson 	if (idx >= lpg->lut_size)
22924e2d05dSBjorn Andersson 		return -ENOMEM;
23024e2d05dSBjorn Andersson 
23124e2d05dSBjorn Andersson 	for (i = 0; i < len; i++) {
23224e2d05dSBjorn Andersson 		val = pattern[i].brightness;
23324e2d05dSBjorn Andersson 
23424e2d05dSBjorn Andersson 		regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i),
23524e2d05dSBjorn Andersson 				  &val, sizeof(val));
23624e2d05dSBjorn Andersson 	}
23724e2d05dSBjorn Andersson 
23824e2d05dSBjorn Andersson 	bitmap_set(lpg->lut_bitmap, idx, len);
23924e2d05dSBjorn Andersson 
24024e2d05dSBjorn Andersson 	*lo_idx = idx;
24124e2d05dSBjorn Andersson 	*hi_idx = idx + len - 1;
24224e2d05dSBjorn Andersson 
24324e2d05dSBjorn Andersson 	return 0;
24424e2d05dSBjorn Andersson }
24524e2d05dSBjorn Andersson 
lpg_lut_free(struct lpg * lpg,unsigned int lo_idx,unsigned int hi_idx)24624e2d05dSBjorn Andersson static void lpg_lut_free(struct lpg *lpg, unsigned int lo_idx, unsigned int hi_idx)
24724e2d05dSBjorn Andersson {
24824e2d05dSBjorn Andersson 	int len;
24924e2d05dSBjorn Andersson 
25024e2d05dSBjorn Andersson 	len = hi_idx - lo_idx + 1;
25124e2d05dSBjorn Andersson 	if (len == 1)
25224e2d05dSBjorn Andersson 		return;
25324e2d05dSBjorn Andersson 
25424e2d05dSBjorn Andersson 	bitmap_clear(lpg->lut_bitmap, lo_idx, len);
25524e2d05dSBjorn Andersson }
25624e2d05dSBjorn Andersson 
lpg_lut_sync(struct lpg * lpg,unsigned int mask)25724e2d05dSBjorn Andersson static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
25824e2d05dSBjorn Andersson {
25924e2d05dSBjorn Andersson 	return regmap_write(lpg->map, lpg->lut_base + RAMP_CONTROL_REG, mask);
26024e2d05dSBjorn Andersson }
26124e2d05dSBjorn Andersson 
26224e2d05dSBjorn Andersson static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
263b00d2ed3SAnjelique Melendez static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
26424e2d05dSBjorn Andersson static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
265b00d2ed3SAnjelique Melendez static const unsigned int lpg_pwm_resolution[] =  {9};
266b00d2ed3SAnjelique Melendez static const unsigned int lpg_pwm_resolution_hi_res[] =  {8, 9, 10, 11, 12, 13, 14, 15};
26724e2d05dSBjorn Andersson 
lpg_calc_freq(struct lpg_channel * chan,uint64_t period)26824e2d05dSBjorn Andersson static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
26924e2d05dSBjorn Andersson {
270b00d2ed3SAnjelique Melendez 	unsigned int i, pwm_resolution_count, best_pwm_resolution_sel = 0;
271b00d2ed3SAnjelique Melendez 	const unsigned int *clk_rate_arr, *pwm_resolution_arr;
272b00d2ed3SAnjelique Melendez 	unsigned int clk_sel, clk_len, best_clk = 0;
27324e2d05dSBjorn Andersson 	unsigned int div, best_div = 0;
27424e2d05dSBjorn Andersson 	unsigned int m, best_m = 0;
275b00d2ed3SAnjelique Melendez 	unsigned int resolution;
27624e2d05dSBjorn Andersson 	unsigned int error;
27724e2d05dSBjorn Andersson 	unsigned int best_err = UINT_MAX;
278b00d2ed3SAnjelique Melendez 	u64 max_period, min_period;
27924e2d05dSBjorn Andersson 	u64 best_period = 0;
280b00d2ed3SAnjelique Melendez 	u64 max_res;
28124e2d05dSBjorn Andersson 
28224e2d05dSBjorn Andersson 	/*
28324e2d05dSBjorn Andersson 	 * The PWM period is determined by:
28424e2d05dSBjorn Andersson 	 *
28524e2d05dSBjorn Andersson 	 *          resolution * pre_div * 2^M
28624e2d05dSBjorn Andersson 	 * period = --------------------------
28724e2d05dSBjorn Andersson 	 *                   refclk
28824e2d05dSBjorn Andersson 	 *
289b00d2ed3SAnjelique Melendez 	 * Resolution = 2^9 bits for PWM or
290b00d2ed3SAnjelique Melendez 	 *              2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
291b00d2ed3SAnjelique Melendez 	 * pre_div = {1, 3, 5, 6} and
29224e2d05dSBjorn Andersson 	 * M = [0..7].
29324e2d05dSBjorn Andersson 	 *
294b00d2ed3SAnjelique Melendez 	 * This allows for periods between 27uS and 384s for PWM channels and periods between
295b00d2ed3SAnjelique Melendez 	 * 3uS and 24576s for high resolution PWMs.
296b00d2ed3SAnjelique Melendez 	 * The PWM framework wants a period of equal or lower length than requested,
297b00d2ed3SAnjelique Melendez 	 * reject anything below minimum period.
29824e2d05dSBjorn Andersson 	 */
299b00d2ed3SAnjelique Melendez 
300b00d2ed3SAnjelique Melendez 	if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
301b00d2ed3SAnjelique Melendez 		clk_rate_arr = lpg_clk_rates_hi_res;
302b00d2ed3SAnjelique Melendez 		clk_len = ARRAY_SIZE(lpg_clk_rates_hi_res);
303b00d2ed3SAnjelique Melendez 		pwm_resolution_arr = lpg_pwm_resolution_hi_res;
304b00d2ed3SAnjelique Melendez 		pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution_hi_res);
305b00d2ed3SAnjelique Melendez 		max_res = LPG_RESOLUTION_15BIT;
306b00d2ed3SAnjelique Melendez 	} else {
307b00d2ed3SAnjelique Melendez 		clk_rate_arr = lpg_clk_rates;
308b00d2ed3SAnjelique Melendez 		clk_len = ARRAY_SIZE(lpg_clk_rates);
309b00d2ed3SAnjelique Melendez 		pwm_resolution_arr = lpg_pwm_resolution;
310b00d2ed3SAnjelique Melendez 		pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution);
311b00d2ed3SAnjelique Melendez 		max_res = LPG_RESOLUTION_9BIT;
312b00d2ed3SAnjelique Melendez 	}
313b00d2ed3SAnjelique Melendez 
314*b05d3946SBjorn Andersson 	min_period = div64_u64((u64)NSEC_PER_SEC * (1 << pwm_resolution_arr[0]),
315*b05d3946SBjorn Andersson 			       clk_rate_arr[clk_len - 1]);
316b00d2ed3SAnjelique Melendez 	if (period <= min_period)
31724e2d05dSBjorn Andersson 		return -EINVAL;
31824e2d05dSBjorn Andersson 
31924e2d05dSBjorn Andersson 	/* Limit period to largest possible value, to avoid overflows */
320*b05d3946SBjorn Andersson 	max_period = div64_u64((u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV * (1 << LPG_MAX_M),
321*b05d3946SBjorn Andersson 			       1024);
32224e2d05dSBjorn Andersson 	if (period > max_period)
32324e2d05dSBjorn Andersson 		period = max_period;
32424e2d05dSBjorn Andersson 
32524e2d05dSBjorn Andersson 	/*
326b00d2ed3SAnjelique Melendez 	 * Search for the pre_div, refclk, resolution and M by solving the rewritten formula
327b00d2ed3SAnjelique Melendez 	 * for each refclk, resolution and pre_div value:
32824e2d05dSBjorn Andersson 	 *
32924e2d05dSBjorn Andersson 	 *                     period * refclk
33024e2d05dSBjorn Andersson 	 * M = log2 -------------------------------------
33124e2d05dSBjorn Andersson 	 *           NSEC_PER_SEC * pre_div * resolution
33224e2d05dSBjorn Andersson 	 */
333b00d2ed3SAnjelique Melendez 
334b00d2ed3SAnjelique Melendez 	for (i = 0; i < pwm_resolution_count; i++) {
335b00d2ed3SAnjelique Melendez 		resolution = 1 << pwm_resolution_arr[i];
336b00d2ed3SAnjelique Melendez 		for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
337b00d2ed3SAnjelique Melendez 			u64 numerator = period * clk_rate_arr[clk_sel];
33824e2d05dSBjorn Andersson 
33924e2d05dSBjorn Andersson 			for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
340b00d2ed3SAnjelique Melendez 				u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] *
341b00d2ed3SAnjelique Melendez 						  resolution;
34224e2d05dSBjorn Andersson 				u64 actual;
34324e2d05dSBjorn Andersson 				u64 ratio;
34424e2d05dSBjorn Andersson 
34524e2d05dSBjorn Andersson 				if (numerator < denominator)
34624e2d05dSBjorn Andersson 					continue;
34724e2d05dSBjorn Andersson 
34824e2d05dSBjorn Andersson 				ratio = div64_u64(numerator, denominator);
34924e2d05dSBjorn Andersson 				m = ilog2(ratio);
35024e2d05dSBjorn Andersson 				if (m > LPG_MAX_M)
35124e2d05dSBjorn Andersson 					m = LPG_MAX_M;
35224e2d05dSBjorn Andersson 
353b00d2ed3SAnjelique Melendez 				actual = DIV_ROUND_UP_ULL(denominator * (1 << m),
354b00d2ed3SAnjelique Melendez 							  clk_rate_arr[clk_sel]);
35524e2d05dSBjorn Andersson 				error = period - actual;
35624e2d05dSBjorn Andersson 				if (error < best_err) {
35724e2d05dSBjorn Andersson 					best_err = error;
35824e2d05dSBjorn Andersson 					best_div = div;
35924e2d05dSBjorn Andersson 					best_m = m;
36024e2d05dSBjorn Andersson 					best_clk = clk_sel;
36124e2d05dSBjorn Andersson 					best_period = actual;
362b00d2ed3SAnjelique Melendez 					best_pwm_resolution_sel = i;
36324e2d05dSBjorn Andersson 				}
36424e2d05dSBjorn Andersson 			}
36524e2d05dSBjorn Andersson 		}
366b00d2ed3SAnjelique Melendez 	}
36724e2d05dSBjorn Andersson 	chan->clk_sel = best_clk;
36824e2d05dSBjorn Andersson 	chan->pre_div_sel = best_div;
36924e2d05dSBjorn Andersson 	chan->pre_div_exp = best_m;
37024e2d05dSBjorn Andersson 	chan->period = best_period;
371b00d2ed3SAnjelique Melendez 	chan->pwm_resolution_sel = best_pwm_resolution_sel;
37224e2d05dSBjorn Andersson 	return 0;
37324e2d05dSBjorn Andersson }
37424e2d05dSBjorn Andersson 
lpg_calc_duty(struct lpg_channel * chan,uint64_t duty)37524e2d05dSBjorn Andersson static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
37624e2d05dSBjorn Andersson {
377b00d2ed3SAnjelique Melendez 	unsigned int max;
37824e2d05dSBjorn Andersson 	unsigned int val;
379b00d2ed3SAnjelique Melendez 	unsigned int clk_rate;
38024e2d05dSBjorn Andersson 
381b00d2ed3SAnjelique Melendez 	if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
382b00d2ed3SAnjelique Melendez 		max = LPG_RESOLUTION_15BIT - 1;
383b00d2ed3SAnjelique Melendez 		clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
384b00d2ed3SAnjelique Melendez 	} else {
385b00d2ed3SAnjelique Melendez 		max = LPG_RESOLUTION_9BIT - 1;
386b00d2ed3SAnjelique Melendez 		clk_rate = lpg_clk_rates[chan->clk_sel];
387b00d2ed3SAnjelique Melendez 	}
388b00d2ed3SAnjelique Melendez 
389b00d2ed3SAnjelique Melendez 	val = div64_u64(duty * clk_rate,
39024e2d05dSBjorn Andersson 			(u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp));
39124e2d05dSBjorn Andersson 
39224e2d05dSBjorn Andersson 	chan->pwm_value = min(val, max);
39324e2d05dSBjorn Andersson }
39424e2d05dSBjorn Andersson 
lpg_apply_freq(struct lpg_channel * chan)39524e2d05dSBjorn Andersson static void lpg_apply_freq(struct lpg_channel *chan)
39624e2d05dSBjorn Andersson {
39724e2d05dSBjorn Andersson 	unsigned long val;
39824e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
39924e2d05dSBjorn Andersson 
40024e2d05dSBjorn Andersson 	if (!chan->enabled)
40124e2d05dSBjorn Andersson 		return;
40224e2d05dSBjorn Andersson 
40324e2d05dSBjorn Andersson 	val = chan->clk_sel;
40424e2d05dSBjorn Andersson 
405b00d2ed3SAnjelique Melendez 	/* Specify resolution, based on the subtype of the channel */
40624e2d05dSBjorn Andersson 	switch (chan->subtype) {
40724e2d05dSBjorn Andersson 	case LPG_SUBTYPE_LPG:
40824e2d05dSBjorn Andersson 		val |= GENMASK(5, 4);
40924e2d05dSBjorn Andersson 		break;
41024e2d05dSBjorn Andersson 	case LPG_SUBTYPE_PWM:
41124e2d05dSBjorn Andersson 		val |= BIT(2);
41224e2d05dSBjorn Andersson 		break;
413b00d2ed3SAnjelique Melendez 	case LPG_SUBTYPE_HI_RES_PWM:
414b00d2ed3SAnjelique Melendez 		val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
415b00d2ed3SAnjelique Melendez 		break;
41624e2d05dSBjorn Andersson 	case LPG_SUBTYPE_LPG_LITE:
41724e2d05dSBjorn Andersson 	default:
41824e2d05dSBjorn Andersson 		val |= BIT(4);
41924e2d05dSBjorn Andersson 		break;
42024e2d05dSBjorn Andersson 	}
42124e2d05dSBjorn Andersson 
42224e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_SIZE_CLK_REG, val);
42324e2d05dSBjorn Andersson 
42424e2d05dSBjorn Andersson 	val = FIELD_PREP(PWM_FREQ_PRE_DIV_MASK, chan->pre_div_sel) |
42524e2d05dSBjorn Andersson 	      FIELD_PREP(PWM_FREQ_EXP_MASK, chan->pre_div_exp);
42624e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_PREDIV_CLK_REG, val);
42724e2d05dSBjorn Andersson }
42824e2d05dSBjorn Andersson 
42924e2d05dSBjorn Andersson #define LPG_ENABLE_GLITCH_REMOVAL	BIT(5)
43024e2d05dSBjorn Andersson 
lpg_enable_glitch(struct lpg_channel * chan)43124e2d05dSBjorn Andersson static void lpg_enable_glitch(struct lpg_channel *chan)
43224e2d05dSBjorn Andersson {
43324e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
43424e2d05dSBjorn Andersson 
43524e2d05dSBjorn Andersson 	regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG,
43624e2d05dSBjorn Andersson 			   LPG_ENABLE_GLITCH_REMOVAL, 0);
43724e2d05dSBjorn Andersson }
43824e2d05dSBjorn Andersson 
lpg_disable_glitch(struct lpg_channel * chan)43924e2d05dSBjorn Andersson static void lpg_disable_glitch(struct lpg_channel *chan)
44024e2d05dSBjorn Andersson {
44124e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
44224e2d05dSBjorn Andersson 
44324e2d05dSBjorn Andersson 	regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG,
44424e2d05dSBjorn Andersson 			   LPG_ENABLE_GLITCH_REMOVAL,
44524e2d05dSBjorn Andersson 			   LPG_ENABLE_GLITCH_REMOVAL);
44624e2d05dSBjorn Andersson }
44724e2d05dSBjorn Andersson 
lpg_apply_pwm_value(struct lpg_channel * chan)44824e2d05dSBjorn Andersson static void lpg_apply_pwm_value(struct lpg_channel *chan)
44924e2d05dSBjorn Andersson {
45024e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
45124e2d05dSBjorn Andersson 	u16 val = chan->pwm_value;
45224e2d05dSBjorn Andersson 
45324e2d05dSBjorn Andersson 	if (!chan->enabled)
45424e2d05dSBjorn Andersson 		return;
45524e2d05dSBjorn Andersson 
45624e2d05dSBjorn Andersson 	regmap_bulk_write(lpg->map, chan->base + PWM_VALUE_REG, &val, sizeof(val));
45724e2d05dSBjorn Andersson }
45824e2d05dSBjorn Andersson 
45924e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_LO_TO_HI	BIT(4)
46024e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_REPEAT	BIT(3)
46124e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_TOGGLE	BIT(2)
46224e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_PAUSE_HI	BIT(1)
46324e2d05dSBjorn Andersson #define LPG_PATTERN_CONFIG_PAUSE_LO	BIT(0)
46424e2d05dSBjorn Andersson 
lpg_apply_lut_control(struct lpg_channel * chan)46524e2d05dSBjorn Andersson static void lpg_apply_lut_control(struct lpg_channel *chan)
46624e2d05dSBjorn Andersson {
46724e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
46824e2d05dSBjorn Andersson 	unsigned int hi_pause;
46924e2d05dSBjorn Andersson 	unsigned int lo_pause;
47024e2d05dSBjorn Andersson 	unsigned int conf = 0;
47124e2d05dSBjorn Andersson 	unsigned int lo_idx = chan->pattern_lo_idx;
47224e2d05dSBjorn Andersson 	unsigned int hi_idx = chan->pattern_hi_idx;
47324e2d05dSBjorn Andersson 	u16 step = chan->ramp_tick_ms;
47424e2d05dSBjorn Andersson 
47524e2d05dSBjorn Andersson 	if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx)
47624e2d05dSBjorn Andersson 		return;
47724e2d05dSBjorn Andersson 
47824e2d05dSBjorn Andersson 	hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, step);
47924e2d05dSBjorn Andersson 	lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, step);
48024e2d05dSBjorn Andersson 
48124e2d05dSBjorn Andersson 	if (!chan->ramp_reverse)
48224e2d05dSBjorn Andersson 		conf |= LPG_PATTERN_CONFIG_LO_TO_HI;
48324e2d05dSBjorn Andersson 	if (!chan->ramp_oneshot)
48424e2d05dSBjorn Andersson 		conf |= LPG_PATTERN_CONFIG_REPEAT;
48524e2d05dSBjorn Andersson 	if (chan->ramp_ping_pong)
48624e2d05dSBjorn Andersson 		conf |= LPG_PATTERN_CONFIG_TOGGLE;
48724e2d05dSBjorn Andersson 	if (chan->ramp_hi_pause_ms)
48824e2d05dSBjorn Andersson 		conf |= LPG_PATTERN_CONFIG_PAUSE_HI;
48924e2d05dSBjorn Andersson 	if (chan->ramp_lo_pause_ms)
49024e2d05dSBjorn Andersson 		conf |= LPG_PATTERN_CONFIG_PAUSE_LO;
49124e2d05dSBjorn Andersson 
49224e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_PATTERN_CONFIG_REG, conf);
49324e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_HI_IDX_REG, hi_idx);
49424e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_LO_IDX_REG, lo_idx);
49524e2d05dSBjorn Andersson 
49624e2d05dSBjorn Andersson 	regmap_bulk_write(lpg->map, chan->base + LPG_RAMP_DURATION_REG, &step, sizeof(step));
49724e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_HI_PAUSE_REG, hi_pause);
49824e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + LPG_LO_PAUSE_REG, lo_pause);
49924e2d05dSBjorn Andersson }
50024e2d05dSBjorn Andersson 
50124e2d05dSBjorn Andersson #define LPG_ENABLE_CONTROL_OUTPUT		BIT(7)
50224e2d05dSBjorn Andersson #define LPG_ENABLE_CONTROL_BUFFER_TRISTATE	BIT(5)
50324e2d05dSBjorn Andersson #define LPG_ENABLE_CONTROL_SRC_PWM		BIT(2)
50424e2d05dSBjorn Andersson #define LPG_ENABLE_CONTROL_RAMP_GEN		BIT(1)
50524e2d05dSBjorn Andersson 
lpg_apply_control(struct lpg_channel * chan)50624e2d05dSBjorn Andersson static void lpg_apply_control(struct lpg_channel *chan)
50724e2d05dSBjorn Andersson {
50824e2d05dSBjorn Andersson 	unsigned int ctrl;
50924e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
51024e2d05dSBjorn Andersson 
51124e2d05dSBjorn Andersson 	ctrl = LPG_ENABLE_CONTROL_BUFFER_TRISTATE;
51224e2d05dSBjorn Andersson 
51324e2d05dSBjorn Andersson 	if (chan->enabled)
51424e2d05dSBjorn Andersson 		ctrl |= LPG_ENABLE_CONTROL_OUTPUT;
51524e2d05dSBjorn Andersson 
51624e2d05dSBjorn Andersson 	if (chan->pattern_lo_idx != chan->pattern_hi_idx)
51724e2d05dSBjorn Andersson 		ctrl |= LPG_ENABLE_CONTROL_RAMP_GEN;
51824e2d05dSBjorn Andersson 	else
51924e2d05dSBjorn Andersson 		ctrl |= LPG_ENABLE_CONTROL_SRC_PWM;
52024e2d05dSBjorn Andersson 
52124e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + PWM_ENABLE_CONTROL_REG, ctrl);
52224e2d05dSBjorn Andersson 
52324e2d05dSBjorn Andersson 	/*
52424e2d05dSBjorn Andersson 	 * Due to LPG hardware bug, in the PWM mode, having enabled PWM,
52524e2d05dSBjorn Andersson 	 * We have to write PWM values one more time.
52624e2d05dSBjorn Andersson 	 */
52724e2d05dSBjorn Andersson 	if (chan->enabled)
52824e2d05dSBjorn Andersson 		lpg_apply_pwm_value(chan);
52924e2d05dSBjorn Andersson }
53024e2d05dSBjorn Andersson 
53124e2d05dSBjorn Andersson #define LPG_SYNC_PWM	BIT(0)
53224e2d05dSBjorn Andersson 
lpg_apply_sync(struct lpg_channel * chan)53324e2d05dSBjorn Andersson static void lpg_apply_sync(struct lpg_channel *chan)
53424e2d05dSBjorn Andersson {
53524e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
53624e2d05dSBjorn Andersson 
53724e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + PWM_SYNC_REG, LPG_SYNC_PWM);
53824e2d05dSBjorn Andersson }
53924e2d05dSBjorn Andersson 
lpg_parse_dtest(struct lpg * lpg)54024e2d05dSBjorn Andersson static int lpg_parse_dtest(struct lpg *lpg)
54124e2d05dSBjorn Andersson {
54224e2d05dSBjorn Andersson 	struct lpg_channel *chan;
54324e2d05dSBjorn Andersson 	struct device_node *np = lpg->dev->of_node;
54424e2d05dSBjorn Andersson 	int count;
54524e2d05dSBjorn Andersson 	int ret;
54624e2d05dSBjorn Andersson 	int i;
54724e2d05dSBjorn Andersson 
54824e2d05dSBjorn Andersson 	count = of_property_count_u32_elems(np, "qcom,dtest");
54924e2d05dSBjorn Andersson 	if (count == -EINVAL) {
55024e2d05dSBjorn Andersson 		return 0;
55124e2d05dSBjorn Andersson 	} else if (count < 0) {
55224e2d05dSBjorn Andersson 		ret = count;
55324e2d05dSBjorn Andersson 		goto err_malformed;
55424e2d05dSBjorn Andersson 	} else if (count != lpg->data->num_channels * 2) {
55524e2d05dSBjorn Andersson 		dev_err(lpg->dev, "qcom,dtest needs to be %d items\n",
55624e2d05dSBjorn Andersson 			lpg->data->num_channels * 2);
55724e2d05dSBjorn Andersson 		return -EINVAL;
55824e2d05dSBjorn Andersson 	}
55924e2d05dSBjorn Andersson 
56024e2d05dSBjorn Andersson 	for (i = 0; i < lpg->data->num_channels; i++) {
56124e2d05dSBjorn Andersson 		chan = &lpg->channels[i];
56224e2d05dSBjorn Andersson 
56324e2d05dSBjorn Andersson 		ret = of_property_read_u32_index(np, "qcom,dtest", i * 2,
56424e2d05dSBjorn Andersson 						 &chan->dtest_line);
56524e2d05dSBjorn Andersson 		if (ret)
56624e2d05dSBjorn Andersson 			goto err_malformed;
56724e2d05dSBjorn Andersson 
56824e2d05dSBjorn Andersson 		ret = of_property_read_u32_index(np, "qcom,dtest", i * 2 + 1,
56924e2d05dSBjorn Andersson 						 &chan->dtest_value);
57024e2d05dSBjorn Andersson 		if (ret)
57124e2d05dSBjorn Andersson 			goto err_malformed;
57224e2d05dSBjorn Andersson 	}
57324e2d05dSBjorn Andersson 
57424e2d05dSBjorn Andersson 	return 0;
57524e2d05dSBjorn Andersson 
57624e2d05dSBjorn Andersson err_malformed:
57724e2d05dSBjorn Andersson 	dev_err(lpg->dev, "malformed qcom,dtest\n");
57824e2d05dSBjorn Andersson 	return ret;
57924e2d05dSBjorn Andersson }
58024e2d05dSBjorn Andersson 
lpg_apply_dtest(struct lpg_channel * chan)58124e2d05dSBjorn Andersson static void lpg_apply_dtest(struct lpg_channel *chan)
58224e2d05dSBjorn Andersson {
58324e2d05dSBjorn Andersson 	struct lpg *lpg = chan->lpg;
58424e2d05dSBjorn Andersson 
58524e2d05dSBjorn Andersson 	if (!chan->dtest_line)
58624e2d05dSBjorn Andersson 		return;
58724e2d05dSBjorn Andersson 
58824e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + PWM_SEC_ACCESS_REG, 0xa5);
58924e2d05dSBjorn Andersson 	regmap_write(lpg->map, chan->base + PWM_DTEST_REG(chan->dtest_line),
59024e2d05dSBjorn Andersson 		     chan->dtest_value);
59124e2d05dSBjorn Andersson }
59224e2d05dSBjorn Andersson 
lpg_apply(struct lpg_channel * chan)59324e2d05dSBjorn Andersson static void lpg_apply(struct lpg_channel *chan)
59424e2d05dSBjorn Andersson {
59524e2d05dSBjorn Andersson 	lpg_disable_glitch(chan);
59624e2d05dSBjorn Andersson 	lpg_apply_freq(chan);
59724e2d05dSBjorn Andersson 	lpg_apply_pwm_value(chan);
59824e2d05dSBjorn Andersson 	lpg_apply_control(chan);
59924e2d05dSBjorn Andersson 	lpg_apply_sync(chan);
60024e2d05dSBjorn Andersson 	lpg_apply_lut_control(chan);
60124e2d05dSBjorn Andersson 	lpg_enable_glitch(chan);
60224e2d05dSBjorn Andersson }
60324e2d05dSBjorn Andersson 
lpg_brightness_set(struct lpg_led * led,struct led_classdev * cdev,struct mc_subled * subleds)60424e2d05dSBjorn Andersson static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev,
60524e2d05dSBjorn Andersson 			       struct mc_subled *subleds)
60624e2d05dSBjorn Andersson {
60724e2d05dSBjorn Andersson 	enum led_brightness brightness;
60824e2d05dSBjorn Andersson 	struct lpg_channel *chan;
60924e2d05dSBjorn Andersson 	unsigned int triled_enabled = 0;
61024e2d05dSBjorn Andersson 	unsigned int triled_mask = 0;
61124e2d05dSBjorn Andersson 	unsigned int lut_mask = 0;
61224e2d05dSBjorn Andersson 	unsigned int duty;
61324e2d05dSBjorn Andersson 	struct lpg *lpg = led->lpg;
61424e2d05dSBjorn Andersson 	int i;
61524e2d05dSBjorn Andersson 
61624e2d05dSBjorn Andersson 	for (i = 0; i < led->num_channels; i++) {
61724e2d05dSBjorn Andersson 		chan = led->channels[i];
61824e2d05dSBjorn Andersson 		brightness = subleds[i].brightness;
61924e2d05dSBjorn Andersson 
62024e2d05dSBjorn Andersson 		if (brightness == LED_OFF) {
62124e2d05dSBjorn Andersson 			chan->enabled = false;
62224e2d05dSBjorn Andersson 			chan->ramp_enabled = false;
62324e2d05dSBjorn Andersson 		} else if (chan->pattern_lo_idx != chan->pattern_hi_idx) {
62424e2d05dSBjorn Andersson 			lpg_calc_freq(chan, NSEC_PER_MSEC);
62524e2d05dSBjorn Andersson 
62624e2d05dSBjorn Andersson 			chan->enabled = true;
62724e2d05dSBjorn Andersson 			chan->ramp_enabled = true;
62824e2d05dSBjorn Andersson 
62924e2d05dSBjorn Andersson 			lut_mask |= chan->lut_mask;
63024e2d05dSBjorn Andersson 			triled_enabled |= chan->triled_mask;
63124e2d05dSBjorn Andersson 		} else {
63224e2d05dSBjorn Andersson 			lpg_calc_freq(chan, NSEC_PER_MSEC);
63324e2d05dSBjorn Andersson 
63424e2d05dSBjorn Andersson 			duty = div_u64(brightness * chan->period, cdev->max_brightness);
63524e2d05dSBjorn Andersson 			lpg_calc_duty(chan, duty);
63624e2d05dSBjorn Andersson 			chan->enabled = true;
63724e2d05dSBjorn Andersson 			chan->ramp_enabled = false;
63824e2d05dSBjorn Andersson 
63924e2d05dSBjorn Andersson 			triled_enabled |= chan->triled_mask;
64024e2d05dSBjorn Andersson 		}
64124e2d05dSBjorn Andersson 
64224e2d05dSBjorn Andersson 		triled_mask |= chan->triled_mask;
64324e2d05dSBjorn Andersson 
64424e2d05dSBjorn Andersson 		lpg_apply(chan);
64524e2d05dSBjorn Andersson 	}
64624e2d05dSBjorn Andersson 
64724e2d05dSBjorn Andersson 	/* Toggle triled lines */
64824e2d05dSBjorn Andersson 	if (triled_mask)
64924e2d05dSBjorn Andersson 		triled_set(lpg, triled_mask, triled_enabled);
65024e2d05dSBjorn Andersson 
65124e2d05dSBjorn Andersson 	/* Trigger start of ramp generator(s) */
65224e2d05dSBjorn Andersson 	if (lut_mask)
65324e2d05dSBjorn Andersson 		lpg_lut_sync(lpg, lut_mask);
65424e2d05dSBjorn Andersson }
65524e2d05dSBjorn Andersson 
lpg_brightness_single_set(struct led_classdev * cdev,enum led_brightness value)6563031993bSDmitry Baryshkov static int lpg_brightness_single_set(struct led_classdev *cdev,
65724e2d05dSBjorn Andersson 				     enum led_brightness value)
65824e2d05dSBjorn Andersson {
65924e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
66024e2d05dSBjorn Andersson 	struct mc_subled info;
66124e2d05dSBjorn Andersson 
66224e2d05dSBjorn Andersson 	mutex_lock(&led->lpg->lock);
66324e2d05dSBjorn Andersson 
66424e2d05dSBjorn Andersson 	info.brightness = value;
66524e2d05dSBjorn Andersson 	lpg_brightness_set(led, cdev, &info);
66624e2d05dSBjorn Andersson 
66724e2d05dSBjorn Andersson 	mutex_unlock(&led->lpg->lock);
6683031993bSDmitry Baryshkov 
6693031993bSDmitry Baryshkov 	return 0;
67024e2d05dSBjorn Andersson }
67124e2d05dSBjorn Andersson 
lpg_brightness_mc_set(struct led_classdev * cdev,enum led_brightness value)6723031993bSDmitry Baryshkov static int lpg_brightness_mc_set(struct led_classdev *cdev,
67324e2d05dSBjorn Andersson 				 enum led_brightness value)
67424e2d05dSBjorn Andersson {
67524e2d05dSBjorn Andersson 	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
67624e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
67724e2d05dSBjorn Andersson 
67824e2d05dSBjorn Andersson 	mutex_lock(&led->lpg->lock);
67924e2d05dSBjorn Andersson 
68024e2d05dSBjorn Andersson 	led_mc_calc_color_components(mc, value);
68124e2d05dSBjorn Andersson 	lpg_brightness_set(led, cdev, mc->subled_info);
68224e2d05dSBjorn Andersson 
68324e2d05dSBjorn Andersson 	mutex_unlock(&led->lpg->lock);
6843031993bSDmitry Baryshkov 
6853031993bSDmitry Baryshkov 	return 0;
68624e2d05dSBjorn Andersson }
68724e2d05dSBjorn Andersson 
lpg_blink_set(struct lpg_led * led,unsigned long * delay_on,unsigned long * delay_off)68824e2d05dSBjorn Andersson static int lpg_blink_set(struct lpg_led *led,
68924e2d05dSBjorn Andersson 			 unsigned long *delay_on, unsigned long *delay_off)
69024e2d05dSBjorn Andersson {
69124e2d05dSBjorn Andersson 	struct lpg_channel *chan;
69224e2d05dSBjorn Andersson 	unsigned int period;
69324e2d05dSBjorn Andersson 	unsigned int triled_mask = 0;
69424e2d05dSBjorn Andersson 	struct lpg *lpg = led->lpg;
69524e2d05dSBjorn Andersson 	u64 duty;
69624e2d05dSBjorn Andersson 	int i;
69724e2d05dSBjorn Andersson 
69824e2d05dSBjorn Andersson 	if (!*delay_on && !*delay_off) {
69924e2d05dSBjorn Andersson 		*delay_on = 500;
70024e2d05dSBjorn Andersson 		*delay_off = 500;
70124e2d05dSBjorn Andersson 	}
70224e2d05dSBjorn Andersson 
70324e2d05dSBjorn Andersson 	duty = *delay_on * NSEC_PER_MSEC;
70424e2d05dSBjorn Andersson 	period = (*delay_on + *delay_off) * NSEC_PER_MSEC;
70524e2d05dSBjorn Andersson 
70624e2d05dSBjorn Andersson 	for (i = 0; i < led->num_channels; i++) {
70724e2d05dSBjorn Andersson 		chan = led->channels[i];
70824e2d05dSBjorn Andersson 
70924e2d05dSBjorn Andersson 		lpg_calc_freq(chan, period);
71024e2d05dSBjorn Andersson 		lpg_calc_duty(chan, duty);
71124e2d05dSBjorn Andersson 
71224e2d05dSBjorn Andersson 		chan->enabled = true;
71324e2d05dSBjorn Andersson 		chan->ramp_enabled = false;
71424e2d05dSBjorn Andersson 
71524e2d05dSBjorn Andersson 		triled_mask |= chan->triled_mask;
71624e2d05dSBjorn Andersson 
71724e2d05dSBjorn Andersson 		lpg_apply(chan);
71824e2d05dSBjorn Andersson 	}
71924e2d05dSBjorn Andersson 
72024e2d05dSBjorn Andersson 	/* Enable triled lines */
72124e2d05dSBjorn Andersson 	triled_set(lpg, triled_mask, triled_mask);
72224e2d05dSBjorn Andersson 
72324e2d05dSBjorn Andersson 	chan = led->channels[0];
724b00d2ed3SAnjelique Melendez 	duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION_9BIT);
72524e2d05dSBjorn Andersson 	*delay_on = div_u64(duty, NSEC_PER_MSEC);
72624e2d05dSBjorn Andersson 	*delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC);
72724e2d05dSBjorn Andersson 
72824e2d05dSBjorn Andersson 	return 0;
72924e2d05dSBjorn Andersson }
73024e2d05dSBjorn Andersson 
lpg_blink_single_set(struct led_classdev * cdev,unsigned long * delay_on,unsigned long * delay_off)73124e2d05dSBjorn Andersson static int lpg_blink_single_set(struct led_classdev *cdev,
73224e2d05dSBjorn Andersson 				unsigned long *delay_on, unsigned long *delay_off)
73324e2d05dSBjorn Andersson {
73424e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
73524e2d05dSBjorn Andersson 	int ret;
73624e2d05dSBjorn Andersson 
73724e2d05dSBjorn Andersson 	mutex_lock(&led->lpg->lock);
73824e2d05dSBjorn Andersson 
73924e2d05dSBjorn Andersson 	ret = lpg_blink_set(led, delay_on, delay_off);
74024e2d05dSBjorn Andersson 
74124e2d05dSBjorn Andersson 	mutex_unlock(&led->lpg->lock);
74224e2d05dSBjorn Andersson 
74324e2d05dSBjorn Andersson 	return ret;
74424e2d05dSBjorn Andersson }
74524e2d05dSBjorn Andersson 
lpg_blink_mc_set(struct led_classdev * cdev,unsigned long * delay_on,unsigned long * delay_off)74624e2d05dSBjorn Andersson static int lpg_blink_mc_set(struct led_classdev *cdev,
74724e2d05dSBjorn Andersson 			    unsigned long *delay_on, unsigned long *delay_off)
74824e2d05dSBjorn Andersson {
74924e2d05dSBjorn Andersson 	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
75024e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
75124e2d05dSBjorn Andersson 	int ret;
75224e2d05dSBjorn Andersson 
75324e2d05dSBjorn Andersson 	mutex_lock(&led->lpg->lock);
75424e2d05dSBjorn Andersson 
75524e2d05dSBjorn Andersson 	ret = lpg_blink_set(led, delay_on, delay_off);
75624e2d05dSBjorn Andersson 
75724e2d05dSBjorn Andersson 	mutex_unlock(&led->lpg->lock);
75824e2d05dSBjorn Andersson 
75924e2d05dSBjorn Andersson 	return ret;
76024e2d05dSBjorn Andersson }
76124e2d05dSBjorn Andersson 
lpg_pattern_set(struct lpg_led * led,struct led_pattern * led_pattern,u32 len,int repeat)762e98a860fSBjorn Andersson static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
76324e2d05dSBjorn Andersson 			   u32 len, int repeat)
76424e2d05dSBjorn Andersson {
76524e2d05dSBjorn Andersson 	struct lpg_channel *chan;
76624e2d05dSBjorn Andersson 	struct lpg *lpg = led->lpg;
767e98a860fSBjorn Andersson 	struct led_pattern *pattern;
76824e2d05dSBjorn Andersson 	unsigned int brightness_a;
76924e2d05dSBjorn Andersson 	unsigned int brightness_b;
77024e2d05dSBjorn Andersson 	unsigned int actual_len;
77124e2d05dSBjorn Andersson 	unsigned int hi_pause;
77224e2d05dSBjorn Andersson 	unsigned int lo_pause;
77324e2d05dSBjorn Andersson 	unsigned int delta_t;
77424e2d05dSBjorn Andersson 	unsigned int lo_idx;
77524e2d05dSBjorn Andersson 	unsigned int hi_idx;
77624e2d05dSBjorn Andersson 	unsigned int i;
77724e2d05dSBjorn Andersson 	bool ping_pong = true;
778e98a860fSBjorn Andersson 	int ret = -EINVAL;
77924e2d05dSBjorn Andersson 
78024e2d05dSBjorn Andersson 	/* Hardware only support oneshot or indefinite loops */
78124e2d05dSBjorn Andersson 	if (repeat != -1 && repeat != 1)
78224e2d05dSBjorn Andersson 		return -EINVAL;
78324e2d05dSBjorn Andersson 
78424e2d05dSBjorn Andersson 	/*
785e98a860fSBjorn Andersson 	 * The standardized leds-trigger-pattern format defines that the
786e98a860fSBjorn Andersson 	 * brightness of the LED follows a linear transition from one entry
787e98a860fSBjorn Andersson 	 * in the pattern to the next, over the given delta_t time. It
788e98a860fSBjorn Andersson 	 * describes that the way to perform instant transitions a zero-length
789e98a860fSBjorn Andersson 	 * entry should be added following a pattern entry.
790e98a860fSBjorn Andersson 	 *
791e98a860fSBjorn Andersson 	 * The LPG hardware is only able to perform the latter (no linear
792e98a860fSBjorn Andersson 	 * transitions), so require each entry in the pattern to be followed by
793e98a860fSBjorn Andersson 	 * a zero-length transition.
794e98a860fSBjorn Andersson 	 */
795e98a860fSBjorn Andersson 	if (len % 2)
796e98a860fSBjorn Andersson 		return -EINVAL;
797e98a860fSBjorn Andersson 
798e98a860fSBjorn Andersson 	pattern = kcalloc(len / 2, sizeof(*pattern), GFP_KERNEL);
799e98a860fSBjorn Andersson 	if (!pattern)
800e98a860fSBjorn Andersson 		return -ENOMEM;
801e98a860fSBjorn Andersson 
802e98a860fSBjorn Andersson 	for (i = 0; i < len; i += 2) {
803e98a860fSBjorn Andersson 		if (led_pattern[i].brightness != led_pattern[i + 1].brightness)
804e98a860fSBjorn Andersson 			goto out_free_pattern;
805e98a860fSBjorn Andersson 		if (led_pattern[i + 1].delta_t != 0)
806e98a860fSBjorn Andersson 			goto out_free_pattern;
807e98a860fSBjorn Andersson 
808e98a860fSBjorn Andersson 		pattern[i / 2].brightness = led_pattern[i].brightness;
809e98a860fSBjorn Andersson 		pattern[i / 2].delta_t = led_pattern[i].delta_t;
810e98a860fSBjorn Andersson 	}
811e98a860fSBjorn Andersson 
812e98a860fSBjorn Andersson 	len /= 2;
813e98a860fSBjorn Andersson 
814e98a860fSBjorn Andersson 	/*
81524e2d05dSBjorn Andersson 	 * Specifying a pattern of length 1 causes the hardware to iterate
81624e2d05dSBjorn Andersson 	 * through the entire LUT, so prohibit this.
81724e2d05dSBjorn Andersson 	 */
81824e2d05dSBjorn Andersson 	if (len < 2)
819e98a860fSBjorn Andersson 		goto out_free_pattern;
82024e2d05dSBjorn Andersson 
82124e2d05dSBjorn Andersson 	/*
82224e2d05dSBjorn Andersson 	 * The LPG plays patterns with at a fixed pace, a "low pause" can be
82324e2d05dSBjorn Andersson 	 * used to stretch the first delay of the pattern and a "high pause"
82424e2d05dSBjorn Andersson 	 * the last one.
82524e2d05dSBjorn Andersson 	 *
82624e2d05dSBjorn Andersson 	 * In order to save space the pattern can be played in "ping pong"
82724e2d05dSBjorn Andersson 	 * mode, in which the pattern is first played forward, then "high
82824e2d05dSBjorn Andersson 	 * pause" is applied, then the pattern is played backwards and finally
82924e2d05dSBjorn Andersson 	 * the "low pause" is applied.
83024e2d05dSBjorn Andersson 	 *
83124e2d05dSBjorn Andersson 	 * The middle elements of the pattern are used to determine delta_t and
83224e2d05dSBjorn Andersson 	 * the "low pause" and "high pause" multipliers are derrived from this.
83324e2d05dSBjorn Andersson 	 *
83424e2d05dSBjorn Andersson 	 * The first element in the pattern is used to determine "low pause".
83524e2d05dSBjorn Andersson 	 *
83624e2d05dSBjorn Andersson 	 * If the specified pattern is a palindrome the ping pong mode is
83724e2d05dSBjorn Andersson 	 * enabled. In this scenario the delta_t of the middle entry (i.e. the
83824e2d05dSBjorn Andersson 	 * last in the programmed pattern) determines the "high pause".
83924e2d05dSBjorn Andersson 	 */
84024e2d05dSBjorn Andersson 
84124e2d05dSBjorn Andersson 	/* Detect palindromes and use "ping pong" to reduce LUT usage */
84224e2d05dSBjorn Andersson 	for (i = 0; i < len / 2; i++) {
84324e2d05dSBjorn Andersson 		brightness_a = pattern[i].brightness;
84424e2d05dSBjorn Andersson 		brightness_b = pattern[len - i - 1].brightness;
84524e2d05dSBjorn Andersson 
84624e2d05dSBjorn Andersson 		if (brightness_a != brightness_b) {
84724e2d05dSBjorn Andersson 			ping_pong = false;
84824e2d05dSBjorn Andersson 			break;
84924e2d05dSBjorn Andersson 		}
85024e2d05dSBjorn Andersson 	}
85124e2d05dSBjorn Andersson 
85224e2d05dSBjorn Andersson 	/* The pattern length to be written to the LUT */
85324e2d05dSBjorn Andersson 	if (ping_pong)
85424e2d05dSBjorn Andersson 		actual_len = (len + 1) / 2;
85524e2d05dSBjorn Andersson 	else
85624e2d05dSBjorn Andersson 		actual_len = len;
85724e2d05dSBjorn Andersson 
85824e2d05dSBjorn Andersson 	/*
85924e2d05dSBjorn Andersson 	 * Validate that all delta_t in the pattern are the same, with the
86024e2d05dSBjorn Andersson 	 * exception of the middle element in case of ping_pong.
86124e2d05dSBjorn Andersson 	 */
86224e2d05dSBjorn Andersson 	delta_t = pattern[1].delta_t;
86324e2d05dSBjorn Andersson 	for (i = 2; i < len; i++) {
86424e2d05dSBjorn Andersson 		if (pattern[i].delta_t != delta_t) {
86524e2d05dSBjorn Andersson 			/*
86624e2d05dSBjorn Andersson 			 * Allow last entry in the full or shortened pattern to
86724e2d05dSBjorn Andersson 			 * specify hi pause. Reject other variations.
86824e2d05dSBjorn Andersson 			 */
86924e2d05dSBjorn Andersson 			if (i != actual_len - 1)
870e98a860fSBjorn Andersson 				goto out_free_pattern;
87124e2d05dSBjorn Andersson 		}
87224e2d05dSBjorn Andersson 	}
87324e2d05dSBjorn Andersson 
87424e2d05dSBjorn Andersson 	/* LPG_RAMP_DURATION_REG is a 9bit */
87524e2d05dSBjorn Andersson 	if (delta_t >= BIT(9))
876e98a860fSBjorn Andersson 		goto out_free_pattern;
87724e2d05dSBjorn Andersson 
87824e2d05dSBjorn Andersson 	/* Find "low pause" and "high pause" in the pattern */
87924e2d05dSBjorn Andersson 	lo_pause = pattern[0].delta_t;
88024e2d05dSBjorn Andersson 	hi_pause = pattern[actual_len - 1].delta_t;
88124e2d05dSBjorn Andersson 
88224e2d05dSBjorn Andersson 	mutex_lock(&lpg->lock);
88324e2d05dSBjorn Andersson 	ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
88424e2d05dSBjorn Andersson 	if (ret < 0)
88524e2d05dSBjorn Andersson 		goto out_unlock;
88624e2d05dSBjorn Andersson 
88724e2d05dSBjorn Andersson 	for (i = 0; i < led->num_channels; i++) {
88824e2d05dSBjorn Andersson 		chan = led->channels[i];
88924e2d05dSBjorn Andersson 
89024e2d05dSBjorn Andersson 		chan->ramp_tick_ms = delta_t;
89124e2d05dSBjorn Andersson 		chan->ramp_ping_pong = ping_pong;
89224e2d05dSBjorn Andersson 		chan->ramp_oneshot = repeat != -1;
89324e2d05dSBjorn Andersson 
89424e2d05dSBjorn Andersson 		chan->ramp_lo_pause_ms = lo_pause;
89524e2d05dSBjorn Andersson 		chan->ramp_hi_pause_ms = hi_pause;
89624e2d05dSBjorn Andersson 
89724e2d05dSBjorn Andersson 		chan->pattern_lo_idx = lo_idx;
89824e2d05dSBjorn Andersson 		chan->pattern_hi_idx = hi_idx;
89924e2d05dSBjorn Andersson 	}
90024e2d05dSBjorn Andersson 
90124e2d05dSBjorn Andersson out_unlock:
90224e2d05dSBjorn Andersson 	mutex_unlock(&lpg->lock);
903e98a860fSBjorn Andersson out_free_pattern:
904e98a860fSBjorn Andersson 	kfree(pattern);
90524e2d05dSBjorn Andersson 
90624e2d05dSBjorn Andersson 	return ret;
90724e2d05dSBjorn Andersson }
90824e2d05dSBjorn Andersson 
lpg_pattern_single_set(struct led_classdev * cdev,struct led_pattern * pattern,u32 len,int repeat)90924e2d05dSBjorn Andersson static int lpg_pattern_single_set(struct led_classdev *cdev,
91024e2d05dSBjorn Andersson 				  struct led_pattern *pattern, u32 len,
91124e2d05dSBjorn Andersson 				  int repeat)
91224e2d05dSBjorn Andersson {
91324e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
91424e2d05dSBjorn Andersson 	int ret;
91524e2d05dSBjorn Andersson 
91624e2d05dSBjorn Andersson 	ret = lpg_pattern_set(led, pattern, len, repeat);
91724e2d05dSBjorn Andersson 	if (ret < 0)
91824e2d05dSBjorn Andersson 		return ret;
91924e2d05dSBjorn Andersson 
92024e2d05dSBjorn Andersson 	lpg_brightness_single_set(cdev, LED_FULL);
92124e2d05dSBjorn Andersson 
92224e2d05dSBjorn Andersson 	return 0;
92324e2d05dSBjorn Andersson }
92424e2d05dSBjorn Andersson 
lpg_pattern_mc_set(struct led_classdev * cdev,struct led_pattern * pattern,u32 len,int repeat)92524e2d05dSBjorn Andersson static int lpg_pattern_mc_set(struct led_classdev *cdev,
92624e2d05dSBjorn Andersson 			      struct led_pattern *pattern, u32 len,
92724e2d05dSBjorn Andersson 			      int repeat)
92824e2d05dSBjorn Andersson {
92924e2d05dSBjorn Andersson 	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
93024e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
93124e2d05dSBjorn Andersson 	int ret;
93224e2d05dSBjorn Andersson 
93324e2d05dSBjorn Andersson 	ret = lpg_pattern_set(led, pattern, len, repeat);
93424e2d05dSBjorn Andersson 	if (ret < 0)
93524e2d05dSBjorn Andersson 		return ret;
93624e2d05dSBjorn Andersson 
93724e2d05dSBjorn Andersson 	led_mc_calc_color_components(mc, LED_FULL);
93824e2d05dSBjorn Andersson 	lpg_brightness_set(led, cdev, mc->subled_info);
93924e2d05dSBjorn Andersson 
94024e2d05dSBjorn Andersson 	return 0;
94124e2d05dSBjorn Andersson }
94224e2d05dSBjorn Andersson 
lpg_pattern_clear(struct lpg_led * led)94324e2d05dSBjorn Andersson static int lpg_pattern_clear(struct lpg_led *led)
94424e2d05dSBjorn Andersson {
94524e2d05dSBjorn Andersson 	struct lpg_channel *chan;
94624e2d05dSBjorn Andersson 	struct lpg *lpg = led->lpg;
94724e2d05dSBjorn Andersson 	int i;
94824e2d05dSBjorn Andersson 
94924e2d05dSBjorn Andersson 	mutex_lock(&lpg->lock);
95024e2d05dSBjorn Andersson 
95124e2d05dSBjorn Andersson 	chan = led->channels[0];
95224e2d05dSBjorn Andersson 	lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
95324e2d05dSBjorn Andersson 
95424e2d05dSBjorn Andersson 	for (i = 0; i < led->num_channels; i++) {
95524e2d05dSBjorn Andersson 		chan = led->channels[i];
95624e2d05dSBjorn Andersson 		chan->pattern_lo_idx = 0;
95724e2d05dSBjorn Andersson 		chan->pattern_hi_idx = 0;
95824e2d05dSBjorn Andersson 	}
95924e2d05dSBjorn Andersson 
96024e2d05dSBjorn Andersson 	mutex_unlock(&lpg->lock);
96124e2d05dSBjorn Andersson 
96224e2d05dSBjorn Andersson 	return 0;
96324e2d05dSBjorn Andersson }
96424e2d05dSBjorn Andersson 
lpg_pattern_single_clear(struct led_classdev * cdev)96524e2d05dSBjorn Andersson static int lpg_pattern_single_clear(struct led_classdev *cdev)
96624e2d05dSBjorn Andersson {
96724e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
96824e2d05dSBjorn Andersson 
96924e2d05dSBjorn Andersson 	return lpg_pattern_clear(led);
97024e2d05dSBjorn Andersson }
97124e2d05dSBjorn Andersson 
lpg_pattern_mc_clear(struct led_classdev * cdev)97224e2d05dSBjorn Andersson static int lpg_pattern_mc_clear(struct led_classdev *cdev)
97324e2d05dSBjorn Andersson {
97424e2d05dSBjorn Andersson 	struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
97524e2d05dSBjorn Andersson 	struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
97624e2d05dSBjorn Andersson 
97724e2d05dSBjorn Andersson 	return lpg_pattern_clear(led);
97824e2d05dSBjorn Andersson }
97924e2d05dSBjorn Andersson 
lpg_pwm_request(struct pwm_chip * chip,struct pwm_device * pwm)98024e2d05dSBjorn Andersson static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
98124e2d05dSBjorn Andersson {
98224e2d05dSBjorn Andersson 	struct lpg *lpg = container_of(chip, struct lpg, pwm);
98324e2d05dSBjorn Andersson 	struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
98424e2d05dSBjorn Andersson 
98524e2d05dSBjorn Andersson 	return chan->in_use ? -EBUSY : 0;
98624e2d05dSBjorn Andersson }
98724e2d05dSBjorn Andersson 
98824e2d05dSBjorn Andersson /*
98924e2d05dSBjorn Andersson  * Limitations:
99024e2d05dSBjorn Andersson  * - Updating both duty and period is not done atomically, so the output signal
99124e2d05dSBjorn Andersson  *   will momentarily be a mix of the settings.
99224e2d05dSBjorn Andersson  * - Changed parameters takes effect immediately.
99324e2d05dSBjorn Andersson  * - A disabled channel outputs a logical 0.
99424e2d05dSBjorn Andersson  */
lpg_pwm_apply(struct pwm_chip * chip,struct pwm_device * pwm,const struct pwm_state * state)99524e2d05dSBjorn Andersson static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
99624e2d05dSBjorn Andersson 			 const struct pwm_state *state)
99724e2d05dSBjorn Andersson {
99824e2d05dSBjorn Andersson 	struct lpg *lpg = container_of(chip, struct lpg, pwm);
99924e2d05dSBjorn Andersson 	struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
100024e2d05dSBjorn Andersson 	int ret = 0;
100124e2d05dSBjorn Andersson 
100224e2d05dSBjorn Andersson 	if (state->polarity != PWM_POLARITY_NORMAL)
100324e2d05dSBjorn Andersson 		return -EINVAL;
100424e2d05dSBjorn Andersson 
100524e2d05dSBjorn Andersson 	mutex_lock(&lpg->lock);
100624e2d05dSBjorn Andersson 
100724e2d05dSBjorn Andersson 	if (state->enabled) {
100824e2d05dSBjorn Andersson 		ret = lpg_calc_freq(chan, state->period);
100924e2d05dSBjorn Andersson 		if (ret < 0)
101024e2d05dSBjorn Andersson 			goto out_unlock;
101124e2d05dSBjorn Andersson 
101224e2d05dSBjorn Andersson 		lpg_calc_duty(chan, state->duty_cycle);
101324e2d05dSBjorn Andersson 	}
101424e2d05dSBjorn Andersson 	chan->enabled = state->enabled;
101524e2d05dSBjorn Andersson 
101624e2d05dSBjorn Andersson 	lpg_apply(chan);
101724e2d05dSBjorn Andersson 
101824e2d05dSBjorn Andersson 	triled_set(lpg, chan->triled_mask, chan->enabled ? chan->triled_mask : 0);
101924e2d05dSBjorn Andersson 
102024e2d05dSBjorn Andersson out_unlock:
102124e2d05dSBjorn Andersson 	mutex_unlock(&lpg->lock);
102224e2d05dSBjorn Andersson 
102324e2d05dSBjorn Andersson 	return ret;
102424e2d05dSBjorn Andersson }
102524e2d05dSBjorn Andersson 
lpg_pwm_get_state(struct pwm_chip * chip,struct pwm_device * pwm,struct pwm_state * state)10266c452cffSUwe Kleine-König static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
102724e2d05dSBjorn Andersson 			     struct pwm_state *state)
102824e2d05dSBjorn Andersson {
102924e2d05dSBjorn Andersson 	struct lpg *lpg = container_of(chip, struct lpg, pwm);
103024e2d05dSBjorn Andersson 	struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
1031b00d2ed3SAnjelique Melendez 	unsigned int resolution;
103224e2d05dSBjorn Andersson 	unsigned int pre_div;
103324e2d05dSBjorn Andersson 	unsigned int refclk;
103424e2d05dSBjorn Andersson 	unsigned int val;
103524e2d05dSBjorn Andersson 	unsigned int m;
103624e2d05dSBjorn Andersson 	u16 pwm_value;
103724e2d05dSBjorn Andersson 	int ret;
103824e2d05dSBjorn Andersson 
103924e2d05dSBjorn Andersson 	ret = regmap_read(lpg->map, chan->base + LPG_SIZE_CLK_REG, &val);
104024e2d05dSBjorn Andersson 	if (ret)
1041fea768cfSUwe Kleine-König 		return ret;
104224e2d05dSBjorn Andersson 
1043b00d2ed3SAnjelique Melendez 	if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
1044b00d2ed3SAnjelique Melendez 		refclk = lpg_clk_rates_hi_res[FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val)];
1045b00d2ed3SAnjelique Melendez 		resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
1046b00d2ed3SAnjelique Melendez 	} else {
1047b00d2ed3SAnjelique Melendez 		refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
1048b00d2ed3SAnjelique Melendez 		resolution = 9;
1049b00d2ed3SAnjelique Melendez 	}
1050b00d2ed3SAnjelique Melendez 
105124e2d05dSBjorn Andersson 	if (refclk) {
105224e2d05dSBjorn Andersson 		ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
105324e2d05dSBjorn Andersson 		if (ret)
1054fea768cfSUwe Kleine-König 			return ret;
105524e2d05dSBjorn Andersson 
105624e2d05dSBjorn Andersson 		pre_div = lpg_pre_divs[FIELD_GET(PWM_FREQ_PRE_DIV_MASK, val)];
105724e2d05dSBjorn Andersson 		m = FIELD_GET(PWM_FREQ_EXP_MASK, val);
105824e2d05dSBjorn Andersson 
105924e2d05dSBjorn Andersson 		ret = regmap_bulk_read(lpg->map, chan->base + PWM_VALUE_REG, &pwm_value, sizeof(pwm_value));
106024e2d05dSBjorn Andersson 		if (ret)
1061fea768cfSUwe Kleine-König 			return ret;
106224e2d05dSBjorn Andersson 
1063b00d2ed3SAnjelique Melendez 		state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
1064b00d2ed3SAnjelique Melendez 						 pre_div * (1 << m), refclk);
106524e2d05dSBjorn Andersson 		state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
106624e2d05dSBjorn Andersson 	} else {
106724e2d05dSBjorn Andersson 		state->period = 0;
106824e2d05dSBjorn Andersson 		state->duty_cycle = 0;
106924e2d05dSBjorn Andersson 	}
107024e2d05dSBjorn Andersson 
107124e2d05dSBjorn Andersson 	ret = regmap_read(lpg->map, chan->base + PWM_ENABLE_CONTROL_REG, &val);
107224e2d05dSBjorn Andersson 	if (ret)
1073fea768cfSUwe Kleine-König 		return ret;
107424e2d05dSBjorn Andersson 
107524e2d05dSBjorn Andersson 	state->enabled = FIELD_GET(LPG_ENABLE_CONTROL_OUTPUT, val);
107624e2d05dSBjorn Andersson 	state->polarity = PWM_POLARITY_NORMAL;
107724e2d05dSBjorn Andersson 
107824e2d05dSBjorn Andersson 	if (state->duty_cycle > state->period)
107924e2d05dSBjorn Andersson 		state->duty_cycle = state->period;
10806c452cffSUwe Kleine-König 
10816c452cffSUwe Kleine-König 	return 0;
108224e2d05dSBjorn Andersson }
108324e2d05dSBjorn Andersson 
108424e2d05dSBjorn Andersson static const struct pwm_ops lpg_pwm_ops = {
108524e2d05dSBjorn Andersson 	.request = lpg_pwm_request,
108624e2d05dSBjorn Andersson 	.apply = lpg_pwm_apply,
108724e2d05dSBjorn Andersson 	.get_state = lpg_pwm_get_state,
108824e2d05dSBjorn Andersson 	.owner = THIS_MODULE,
108924e2d05dSBjorn Andersson };
109024e2d05dSBjorn Andersson 
lpg_add_pwm(struct lpg * lpg)109124e2d05dSBjorn Andersson static int lpg_add_pwm(struct lpg *lpg)
109224e2d05dSBjorn Andersson {
109324e2d05dSBjorn Andersson 	int ret;
109424e2d05dSBjorn Andersson 
109524e2d05dSBjorn Andersson 	lpg->pwm.dev = lpg->dev;
109624e2d05dSBjorn Andersson 	lpg->pwm.npwm = lpg->num_channels;
109724e2d05dSBjorn Andersson 	lpg->pwm.ops = &lpg_pwm_ops;
109824e2d05dSBjorn Andersson 
109924e2d05dSBjorn Andersson 	ret = pwmchip_add(&lpg->pwm);
110024e2d05dSBjorn Andersson 	if (ret)
110124e2d05dSBjorn Andersson 		dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret);
110224e2d05dSBjorn Andersson 
110324e2d05dSBjorn Andersson 	return ret;
110424e2d05dSBjorn Andersson }
110524e2d05dSBjorn Andersson 
lpg_parse_channel(struct lpg * lpg,struct device_node * np,struct lpg_channel ** channel)110624e2d05dSBjorn Andersson static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
110724e2d05dSBjorn Andersson 			     struct lpg_channel **channel)
110824e2d05dSBjorn Andersson {
110924e2d05dSBjorn Andersson 	struct lpg_channel *chan;
111024e2d05dSBjorn Andersson 	u32 color = LED_COLOR_ID_GREEN;
111124e2d05dSBjorn Andersson 	u32 reg;
111224e2d05dSBjorn Andersson 	int ret;
111324e2d05dSBjorn Andersson 
111424e2d05dSBjorn Andersson 	ret = of_property_read_u32(np, "reg", &reg);
111524e2d05dSBjorn Andersson 	if (ret || !reg || reg > lpg->num_channels) {
111624e2d05dSBjorn Andersson 		dev_err(lpg->dev, "invalid \"reg\" of %pOFn\n", np);
111724e2d05dSBjorn Andersson 		return -EINVAL;
111824e2d05dSBjorn Andersson 	}
111924e2d05dSBjorn Andersson 
112024e2d05dSBjorn Andersson 	chan = &lpg->channels[reg - 1];
112124e2d05dSBjorn Andersson 	chan->in_use = true;
112224e2d05dSBjorn Andersson 
112324e2d05dSBjorn Andersson 	ret = of_property_read_u32(np, "color", &color);
112424e2d05dSBjorn Andersson 	if (ret < 0 && ret != -EINVAL) {
112524e2d05dSBjorn Andersson 		dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
112624e2d05dSBjorn Andersson 		return ret;
112724e2d05dSBjorn Andersson 	}
112824e2d05dSBjorn Andersson 
112924e2d05dSBjorn Andersson 	chan->color = color;
113024e2d05dSBjorn Andersson 
113124e2d05dSBjorn Andersson 	*channel = chan;
113224e2d05dSBjorn Andersson 
113324e2d05dSBjorn Andersson 	return 0;
113424e2d05dSBjorn Andersson }
113524e2d05dSBjorn Andersson 
lpg_add_led(struct lpg * lpg,struct device_node * np)113624e2d05dSBjorn Andersson static int lpg_add_led(struct lpg *lpg, struct device_node *np)
113724e2d05dSBjorn Andersson {
113824e2d05dSBjorn Andersson 	struct led_init_data init_data = {};
113924e2d05dSBjorn Andersson 	struct led_classdev *cdev;
114024e2d05dSBjorn Andersson 	struct device_node *child;
114124e2d05dSBjorn Andersson 	struct mc_subled *info;
114224e2d05dSBjorn Andersson 	struct lpg_led *led;
114324e2d05dSBjorn Andersson 	const char *state;
114424e2d05dSBjorn Andersson 	int num_channels;
114524e2d05dSBjorn Andersson 	u32 color = 0;
114624e2d05dSBjorn Andersson 	int ret;
114724e2d05dSBjorn Andersson 	int i;
114824e2d05dSBjorn Andersson 
114924e2d05dSBjorn Andersson 	ret = of_property_read_u32(np, "color", &color);
115024e2d05dSBjorn Andersson 	if (ret < 0 && ret != -EINVAL) {
115124e2d05dSBjorn Andersson 		dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np);
115224e2d05dSBjorn Andersson 		return ret;
115324e2d05dSBjorn Andersson 	}
115424e2d05dSBjorn Andersson 
115524e2d05dSBjorn Andersson 	if (color == LED_COLOR_ID_RGB)
115624e2d05dSBjorn Andersson 		num_channels = of_get_available_child_count(np);
115724e2d05dSBjorn Andersson 	else
115824e2d05dSBjorn Andersson 		num_channels = 1;
115924e2d05dSBjorn Andersson 
116024e2d05dSBjorn Andersson 	led = devm_kzalloc(lpg->dev, struct_size(led, channels, num_channels), GFP_KERNEL);
116124e2d05dSBjorn Andersson 	if (!led)
116224e2d05dSBjorn Andersson 		return -ENOMEM;
116324e2d05dSBjorn Andersson 
116424e2d05dSBjorn Andersson 	led->lpg = lpg;
116524e2d05dSBjorn Andersson 	led->num_channels = num_channels;
116624e2d05dSBjorn Andersson 
116724e2d05dSBjorn Andersson 	if (color == LED_COLOR_ID_RGB) {
116824e2d05dSBjorn Andersson 		info = devm_kcalloc(lpg->dev, num_channels, sizeof(*info), GFP_KERNEL);
116924e2d05dSBjorn Andersson 		if (!info)
117024e2d05dSBjorn Andersson 			return -ENOMEM;
117124e2d05dSBjorn Andersson 		i = 0;
117224e2d05dSBjorn Andersson 		for_each_available_child_of_node(np, child) {
117324e2d05dSBjorn Andersson 			ret = lpg_parse_channel(lpg, child, &led->channels[i]);
11748f38f8faSLu Hongfei 			if (ret < 0) {
11758f38f8faSLu Hongfei 				of_node_put(child);
117624e2d05dSBjorn Andersson 				return ret;
11778f38f8faSLu Hongfei 			}
117824e2d05dSBjorn Andersson 
117924e2d05dSBjorn Andersson 			info[i].color_index = led->channels[i]->color;
118024e2d05dSBjorn Andersson 			info[i].intensity = 0;
118124e2d05dSBjorn Andersson 			i++;
118224e2d05dSBjorn Andersson 		}
118324e2d05dSBjorn Andersson 
118424e2d05dSBjorn Andersson 		led->mcdev.subled_info = info;
118524e2d05dSBjorn Andersson 		led->mcdev.num_colors = num_channels;
118624e2d05dSBjorn Andersson 
118724e2d05dSBjorn Andersson 		cdev = &led->mcdev.led_cdev;
11883031993bSDmitry Baryshkov 		cdev->brightness_set_blocking = lpg_brightness_mc_set;
118924e2d05dSBjorn Andersson 		cdev->blink_set = lpg_blink_mc_set;
119024e2d05dSBjorn Andersson 
119124e2d05dSBjorn Andersson 		/* Register pattern accessors only if we have a LUT block */
119224e2d05dSBjorn Andersson 		if (lpg->lut_base) {
119324e2d05dSBjorn Andersson 			cdev->pattern_set = lpg_pattern_mc_set;
119424e2d05dSBjorn Andersson 			cdev->pattern_clear = lpg_pattern_mc_clear;
119524e2d05dSBjorn Andersson 		}
119624e2d05dSBjorn Andersson 	} else {
119724e2d05dSBjorn Andersson 		ret = lpg_parse_channel(lpg, np, &led->channels[0]);
119824e2d05dSBjorn Andersson 		if (ret < 0)
119924e2d05dSBjorn Andersson 			return ret;
120024e2d05dSBjorn Andersson 
120124e2d05dSBjorn Andersson 		cdev = &led->cdev;
12023031993bSDmitry Baryshkov 		cdev->brightness_set_blocking = lpg_brightness_single_set;
120324e2d05dSBjorn Andersson 		cdev->blink_set = lpg_blink_single_set;
120424e2d05dSBjorn Andersson 
120524e2d05dSBjorn Andersson 		/* Register pattern accessors only if we have a LUT block */
120624e2d05dSBjorn Andersson 		if (lpg->lut_base) {
120724e2d05dSBjorn Andersson 			cdev->pattern_set = lpg_pattern_single_set;
120824e2d05dSBjorn Andersson 			cdev->pattern_clear = lpg_pattern_single_clear;
120924e2d05dSBjorn Andersson 		}
121024e2d05dSBjorn Andersson 	}
121124e2d05dSBjorn Andersson 
121224e2d05dSBjorn Andersson 	cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
1213b00d2ed3SAnjelique Melendez 	cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
121424e2d05dSBjorn Andersson 
121524e2d05dSBjorn Andersson 	if (!of_property_read_string(np, "default-state", &state) &&
121624e2d05dSBjorn Andersson 	    !strcmp(state, "on"))
121724e2d05dSBjorn Andersson 		cdev->brightness = cdev->max_brightness;
121824e2d05dSBjorn Andersson 	else
121924e2d05dSBjorn Andersson 		cdev->brightness = LED_OFF;
122024e2d05dSBjorn Andersson 
12213031993bSDmitry Baryshkov 	cdev->brightness_set_blocking(cdev, cdev->brightness);
122224e2d05dSBjorn Andersson 
122324e2d05dSBjorn Andersson 	init_data.fwnode = of_fwnode_handle(np);
122424e2d05dSBjorn Andersson 
122524e2d05dSBjorn Andersson 	if (color == LED_COLOR_ID_RGB)
122624e2d05dSBjorn Andersson 		ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data);
122724e2d05dSBjorn Andersson 	else
122824e2d05dSBjorn Andersson 		ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data);
122924e2d05dSBjorn Andersson 	if (ret)
123024e2d05dSBjorn Andersson 		dev_err(lpg->dev, "unable to register %s\n", cdev->name);
123124e2d05dSBjorn Andersson 
123224e2d05dSBjorn Andersson 	return ret;
123324e2d05dSBjorn Andersson }
123424e2d05dSBjorn Andersson 
lpg_init_channels(struct lpg * lpg)123524e2d05dSBjorn Andersson static int lpg_init_channels(struct lpg *lpg)
123624e2d05dSBjorn Andersson {
123724e2d05dSBjorn Andersson 	const struct lpg_data *data = lpg->data;
123824e2d05dSBjorn Andersson 	struct lpg_channel *chan;
123924e2d05dSBjorn Andersson 	int i;
124024e2d05dSBjorn Andersson 
124124e2d05dSBjorn Andersson 	lpg->num_channels = data->num_channels;
124224e2d05dSBjorn Andersson 	lpg->channels = devm_kcalloc(lpg->dev, data->num_channels,
124324e2d05dSBjorn Andersson 				     sizeof(struct lpg_channel), GFP_KERNEL);
124424e2d05dSBjorn Andersson 	if (!lpg->channels)
124524e2d05dSBjorn Andersson 		return -ENOMEM;
124624e2d05dSBjorn Andersson 
124724e2d05dSBjorn Andersson 	for (i = 0; i < data->num_channels; i++) {
124824e2d05dSBjorn Andersson 		chan = &lpg->channels[i];
124924e2d05dSBjorn Andersson 
125024e2d05dSBjorn Andersson 		chan->lpg = lpg;
125124e2d05dSBjorn Andersson 		chan->base = data->channels[i].base;
125224e2d05dSBjorn Andersson 		chan->triled_mask = data->channels[i].triled_mask;
125324e2d05dSBjorn Andersson 		chan->lut_mask = BIT(i);
125424e2d05dSBjorn Andersson 
125524e2d05dSBjorn Andersson 		regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype);
125624e2d05dSBjorn Andersson 	}
125724e2d05dSBjorn Andersson 
125824e2d05dSBjorn Andersson 	return 0;
125924e2d05dSBjorn Andersson }
126024e2d05dSBjorn Andersson 
lpg_init_triled(struct lpg * lpg)126124e2d05dSBjorn Andersson static int lpg_init_triled(struct lpg *lpg)
126224e2d05dSBjorn Andersson {
126324e2d05dSBjorn Andersson 	struct device_node *np = lpg->dev->of_node;
126424e2d05dSBjorn Andersson 	int ret;
126524e2d05dSBjorn Andersson 
126624e2d05dSBjorn Andersson 	/* Skip initialization if we don't have a triled block */
126724e2d05dSBjorn Andersson 	if (!lpg->data->triled_base)
126824e2d05dSBjorn Andersson 		return 0;
126924e2d05dSBjorn Andersson 
127024e2d05dSBjorn Andersson 	lpg->triled_base = lpg->data->triled_base;
127124e2d05dSBjorn Andersson 	lpg->triled_has_atc_ctl = lpg->data->triled_has_atc_ctl;
127224e2d05dSBjorn Andersson 	lpg->triled_has_src_sel = lpg->data->triled_has_src_sel;
127324e2d05dSBjorn Andersson 
127424e2d05dSBjorn Andersson 	if (lpg->triled_has_src_sel) {
127524e2d05dSBjorn Andersson 		ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src);
127624e2d05dSBjorn Andersson 		if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) {
127724e2d05dSBjorn Andersson 			dev_err(lpg->dev, "invalid power source\n");
127824e2d05dSBjorn Andersson 			return -EINVAL;
127924e2d05dSBjorn Andersson 		}
128024e2d05dSBjorn Andersson 	}
128124e2d05dSBjorn Andersson 
128224e2d05dSBjorn Andersson 	/* Disable automatic trickle charge LED */
128324e2d05dSBjorn Andersson 	if (lpg->triled_has_atc_ctl)
128424e2d05dSBjorn Andersson 		regmap_write(lpg->map, lpg->triled_base + TRI_LED_ATC_CTL, 0);
128524e2d05dSBjorn Andersson 
128624e2d05dSBjorn Andersson 	/* Configure power source */
128724e2d05dSBjorn Andersson 	if (lpg->triled_has_src_sel)
128824e2d05dSBjorn Andersson 		regmap_write(lpg->map, lpg->triled_base + TRI_LED_SRC_SEL, lpg->triled_src);
128924e2d05dSBjorn Andersson 
129024e2d05dSBjorn Andersson 	/* Default all outputs to off */
129124e2d05dSBjorn Andersson 	regmap_write(lpg->map, lpg->triled_base + TRI_LED_EN_CTL, 0);
129224e2d05dSBjorn Andersson 
129324e2d05dSBjorn Andersson 	return 0;
129424e2d05dSBjorn Andersson }
129524e2d05dSBjorn Andersson 
lpg_init_lut(struct lpg * lpg)129624e2d05dSBjorn Andersson static int lpg_init_lut(struct lpg *lpg)
129724e2d05dSBjorn Andersson {
129824e2d05dSBjorn Andersson 	const struct lpg_data *data = lpg->data;
129924e2d05dSBjorn Andersson 
130024e2d05dSBjorn Andersson 	if (!data->lut_base)
130124e2d05dSBjorn Andersson 		return 0;
130224e2d05dSBjorn Andersson 
130324e2d05dSBjorn Andersson 	lpg->lut_base = data->lut_base;
130424e2d05dSBjorn Andersson 	lpg->lut_size = data->lut_size;
130524e2d05dSBjorn Andersson 
130624e2d05dSBjorn Andersson 	lpg->lut_bitmap = devm_bitmap_zalloc(lpg->dev, lpg->lut_size, GFP_KERNEL);
130724e2d05dSBjorn Andersson 	if (!lpg->lut_bitmap)
130824e2d05dSBjorn Andersson 		return -ENOMEM;
130924e2d05dSBjorn Andersson 
131024e2d05dSBjorn Andersson 	return 0;
131124e2d05dSBjorn Andersson }
131224e2d05dSBjorn Andersson 
lpg_probe(struct platform_device * pdev)131324e2d05dSBjorn Andersson static int lpg_probe(struct platform_device *pdev)
131424e2d05dSBjorn Andersson {
131524e2d05dSBjorn Andersson 	struct device_node *np;
131624e2d05dSBjorn Andersson 	struct lpg *lpg;
131724e2d05dSBjorn Andersson 	int ret;
131824e2d05dSBjorn Andersson 	int i;
131924e2d05dSBjorn Andersson 
132024e2d05dSBjorn Andersson 	lpg = devm_kzalloc(&pdev->dev, sizeof(*lpg), GFP_KERNEL);
132124e2d05dSBjorn Andersson 	if (!lpg)
132224e2d05dSBjorn Andersson 		return -ENOMEM;
132324e2d05dSBjorn Andersson 
132424e2d05dSBjorn Andersson 	lpg->data = of_device_get_match_data(&pdev->dev);
132524e2d05dSBjorn Andersson 	if (!lpg->data)
132624e2d05dSBjorn Andersson 		return -EINVAL;
132724e2d05dSBjorn Andersson 
132824e2d05dSBjorn Andersson 	platform_set_drvdata(pdev, lpg);
132924e2d05dSBjorn Andersson 
133024e2d05dSBjorn Andersson 	lpg->dev = &pdev->dev;
133124e2d05dSBjorn Andersson 	mutex_init(&lpg->lock);
133224e2d05dSBjorn Andersson 
133324e2d05dSBjorn Andersson 	lpg->map = dev_get_regmap(pdev->dev.parent, NULL);
133424e2d05dSBjorn Andersson 	if (!lpg->map)
133524e2d05dSBjorn Andersson 		return dev_err_probe(&pdev->dev, -ENXIO, "parent regmap unavailable\n");
133624e2d05dSBjorn Andersson 
133724e2d05dSBjorn Andersson 	ret = lpg_init_channels(lpg);
133824e2d05dSBjorn Andersson 	if (ret < 0)
133924e2d05dSBjorn Andersson 		return ret;
134024e2d05dSBjorn Andersson 
134124e2d05dSBjorn Andersson 	ret = lpg_parse_dtest(lpg);
134224e2d05dSBjorn Andersson 	if (ret < 0)
134324e2d05dSBjorn Andersson 		return ret;
134424e2d05dSBjorn Andersson 
134524e2d05dSBjorn Andersson 	ret = lpg_init_triled(lpg);
134624e2d05dSBjorn Andersson 	if (ret < 0)
134724e2d05dSBjorn Andersson 		return ret;
134824e2d05dSBjorn Andersson 
134924e2d05dSBjorn Andersson 	ret = lpg_init_lut(lpg);
135024e2d05dSBjorn Andersson 	if (ret < 0)
135124e2d05dSBjorn Andersson 		return ret;
135224e2d05dSBjorn Andersson 
135324e2d05dSBjorn Andersson 	for_each_available_child_of_node(pdev->dev.of_node, np) {
135424e2d05dSBjorn Andersson 		ret = lpg_add_led(lpg, np);
13558f38f8faSLu Hongfei 		if (ret) {
13568f38f8faSLu Hongfei 			of_node_put(np);
135724e2d05dSBjorn Andersson 			return ret;
135824e2d05dSBjorn Andersson 		}
13598f38f8faSLu Hongfei 	}
136024e2d05dSBjorn Andersson 
136124e2d05dSBjorn Andersson 	for (i = 0; i < lpg->num_channels; i++)
136224e2d05dSBjorn Andersson 		lpg_apply_dtest(&lpg->channels[i]);
136324e2d05dSBjorn Andersson 
136424e2d05dSBjorn Andersson 	return lpg_add_pwm(lpg);
136524e2d05dSBjorn Andersson }
136624e2d05dSBjorn Andersson 
lpg_remove(struct platform_device * pdev)136724e2d05dSBjorn Andersson static int lpg_remove(struct platform_device *pdev)
136824e2d05dSBjorn Andersson {
136924e2d05dSBjorn Andersson 	struct lpg *lpg = platform_get_drvdata(pdev);
137024e2d05dSBjorn Andersson 
137124e2d05dSBjorn Andersson 	pwmchip_remove(&lpg->pwm);
137224e2d05dSBjorn Andersson 
137324e2d05dSBjorn Andersson 	return 0;
137424e2d05dSBjorn Andersson }
137524e2d05dSBjorn Andersson 
137624e2d05dSBjorn Andersson static const struct lpg_data pm8916_pwm_data = {
137724e2d05dSBjorn Andersson 	.num_channels = 1,
137824e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
137924e2d05dSBjorn Andersson 		{ .base = 0xbc00 },
138024e2d05dSBjorn Andersson 	},
138124e2d05dSBjorn Andersson };
138224e2d05dSBjorn Andersson 
138324e2d05dSBjorn Andersson static const struct lpg_data pm8941_lpg_data = {
138424e2d05dSBjorn Andersson 	.lut_base = 0xb000,
138524e2d05dSBjorn Andersson 	.lut_size = 64,
138624e2d05dSBjorn Andersson 
138724e2d05dSBjorn Andersson 	.triled_base = 0xd000,
138824e2d05dSBjorn Andersson 	.triled_has_atc_ctl = true,
138924e2d05dSBjorn Andersson 	.triled_has_src_sel = true,
139024e2d05dSBjorn Andersson 
139124e2d05dSBjorn Andersson 	.num_channels = 8,
139224e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
139324e2d05dSBjorn Andersson 		{ .base = 0xb100 },
139424e2d05dSBjorn Andersson 		{ .base = 0xb200 },
139524e2d05dSBjorn Andersson 		{ .base = 0xb300 },
139624e2d05dSBjorn Andersson 		{ .base = 0xb400 },
139724e2d05dSBjorn Andersson 		{ .base = 0xb500, .triled_mask = BIT(5) },
139824e2d05dSBjorn Andersson 		{ .base = 0xb600, .triled_mask = BIT(6) },
139924e2d05dSBjorn Andersson 		{ .base = 0xb700, .triled_mask = BIT(7) },
140024e2d05dSBjorn Andersson 		{ .base = 0xb800 },
140124e2d05dSBjorn Andersson 	},
140224e2d05dSBjorn Andersson };
140324e2d05dSBjorn Andersson 
140424e2d05dSBjorn Andersson static const struct lpg_data pm8994_lpg_data = {
140524e2d05dSBjorn Andersson 	.lut_base = 0xb000,
140624e2d05dSBjorn Andersson 	.lut_size = 64,
140724e2d05dSBjorn Andersson 
140824e2d05dSBjorn Andersson 	.num_channels = 6,
140924e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
141024e2d05dSBjorn Andersson 		{ .base = 0xb100 },
141124e2d05dSBjorn Andersson 		{ .base = 0xb200 },
141224e2d05dSBjorn Andersson 		{ .base = 0xb300 },
141324e2d05dSBjorn Andersson 		{ .base = 0xb400 },
141424e2d05dSBjorn Andersson 		{ .base = 0xb500 },
141524e2d05dSBjorn Andersson 		{ .base = 0xb600 },
141624e2d05dSBjorn Andersson 	},
141724e2d05dSBjorn Andersson };
141824e2d05dSBjorn Andersson 
1419d11a79ddSLuca Weiss /* PMI632 uses SDAM instead of LUT for pattern */
1420d11a79ddSLuca Weiss static const struct lpg_data pmi632_lpg_data = {
1421d11a79ddSLuca Weiss 	.triled_base = 0xd000,
1422d11a79ddSLuca Weiss 
1423d11a79ddSLuca Weiss 	.num_channels = 5,
1424d11a79ddSLuca Weiss 	.channels = (const struct lpg_channel_data[]) {
1425d11a79ddSLuca Weiss 		{ .base = 0xb300, .triled_mask = BIT(7) },
1426d11a79ddSLuca Weiss 		{ .base = 0xb400, .triled_mask = BIT(6) },
1427d11a79ddSLuca Weiss 		{ .base = 0xb500, .triled_mask = BIT(5) },
1428d11a79ddSLuca Weiss 		{ .base = 0xb600 },
1429d11a79ddSLuca Weiss 		{ .base = 0xb700 },
1430d11a79ddSLuca Weiss 	},
1431d11a79ddSLuca Weiss };
1432d11a79ddSLuca Weiss 
143324e2d05dSBjorn Andersson static const struct lpg_data pmi8994_lpg_data = {
143424e2d05dSBjorn Andersson 	.lut_base = 0xb000,
143524e2d05dSBjorn Andersson 	.lut_size = 24,
143624e2d05dSBjorn Andersson 
143724e2d05dSBjorn Andersson 	.triled_base = 0xd000,
143824e2d05dSBjorn Andersson 	.triled_has_atc_ctl = true,
143924e2d05dSBjorn Andersson 	.triled_has_src_sel = true,
144024e2d05dSBjorn Andersson 
144124e2d05dSBjorn Andersson 	.num_channels = 4,
144224e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
144324e2d05dSBjorn Andersson 		{ .base = 0xb100, .triled_mask = BIT(5) },
144424e2d05dSBjorn Andersson 		{ .base = 0xb200, .triled_mask = BIT(6) },
144524e2d05dSBjorn Andersson 		{ .base = 0xb300, .triled_mask = BIT(7) },
144624e2d05dSBjorn Andersson 		{ .base = 0xb400 },
144724e2d05dSBjorn Andersson 	},
144824e2d05dSBjorn Andersson };
144924e2d05dSBjorn Andersson 
145024e2d05dSBjorn Andersson static const struct lpg_data pmi8998_lpg_data = {
145124e2d05dSBjorn Andersson 	.lut_base = 0xb000,
145224e2d05dSBjorn Andersson 	.lut_size = 49,
145324e2d05dSBjorn Andersson 
145424e2d05dSBjorn Andersson 	.triled_base = 0xd000,
145524e2d05dSBjorn Andersson 
145624e2d05dSBjorn Andersson 	.num_channels = 6,
145724e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
145824e2d05dSBjorn Andersson 		{ .base = 0xb100 },
145924e2d05dSBjorn Andersson 		{ .base = 0xb200 },
146024e2d05dSBjorn Andersson 		{ .base = 0xb300, .triled_mask = BIT(5) },
146124e2d05dSBjorn Andersson 		{ .base = 0xb400, .triled_mask = BIT(6) },
146224e2d05dSBjorn Andersson 		{ .base = 0xb500, .triled_mask = BIT(7) },
146324e2d05dSBjorn Andersson 		{ .base = 0xb600 },
146424e2d05dSBjorn Andersson 	},
146524e2d05dSBjorn Andersson };
146624e2d05dSBjorn Andersson 
146724e2d05dSBjorn Andersson static const struct lpg_data pm8150b_lpg_data = {
146824e2d05dSBjorn Andersson 	.lut_base = 0xb000,
146924e2d05dSBjorn Andersson 	.lut_size = 24,
147024e2d05dSBjorn Andersson 
147124e2d05dSBjorn Andersson 	.triled_base = 0xd000,
147224e2d05dSBjorn Andersson 
147324e2d05dSBjorn Andersson 	.num_channels = 2,
147424e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
147524e2d05dSBjorn Andersson 		{ .base = 0xb100, .triled_mask = BIT(7) },
147624e2d05dSBjorn Andersson 		{ .base = 0xb200, .triled_mask = BIT(6) },
147724e2d05dSBjorn Andersson 	},
147824e2d05dSBjorn Andersson };
147924e2d05dSBjorn Andersson 
148024e2d05dSBjorn Andersson static const struct lpg_data pm8150l_lpg_data = {
148124e2d05dSBjorn Andersson 	.lut_base = 0xb000,
148224e2d05dSBjorn Andersson 	.lut_size = 48,
148324e2d05dSBjorn Andersson 
148424e2d05dSBjorn Andersson 	.triled_base = 0xd000,
148524e2d05dSBjorn Andersson 
148624e2d05dSBjorn Andersson 	.num_channels = 5,
148724e2d05dSBjorn Andersson 	.channels = (const struct lpg_channel_data[]) {
148824e2d05dSBjorn Andersson 		{ .base = 0xb100, .triled_mask = BIT(7) },
148924e2d05dSBjorn Andersson 		{ .base = 0xb200, .triled_mask = BIT(6) },
149024e2d05dSBjorn Andersson 		{ .base = 0xb300, .triled_mask = BIT(5) },
149124e2d05dSBjorn Andersson 		{ .base = 0xbc00 },
149224e2d05dSBjorn Andersson 		{ .base = 0xbd00 },
149324e2d05dSBjorn Andersson 
149424e2d05dSBjorn Andersson 	},
149524e2d05dSBjorn Andersson };
149624e2d05dSBjorn Andersson 
149796c59c8bSSatya Priya static const struct lpg_data pm8350c_pwm_data = {
149896c59c8bSSatya Priya 	.triled_base = 0xef00,
149996c59c8bSSatya Priya 
150096c59c8bSSatya Priya 	.num_channels = 4,
150196c59c8bSSatya Priya 	.channels = (const struct lpg_channel_data[]) {
150296c59c8bSSatya Priya 		{ .base = 0xe800, .triled_mask = BIT(7) },
150396c59c8bSSatya Priya 		{ .base = 0xe900, .triled_mask = BIT(6) },
150496c59c8bSSatya Priya 		{ .base = 0xea00, .triled_mask = BIT(5) },
150596c59c8bSSatya Priya 		{ .base = 0xeb00 },
150696c59c8bSSatya Priya 	},
150796c59c8bSSatya Priya };
150896c59c8bSSatya Priya 
15097fec6515SAnjelique Melendez static const struct lpg_data pmk8550_pwm_data = {
15107fec6515SAnjelique Melendez 	.num_channels = 2,
15117fec6515SAnjelique Melendez 	.channels = (const struct lpg_channel_data[]) {
15127fec6515SAnjelique Melendez 		{ .base = 0xe800 },
15137fec6515SAnjelique Melendez 		{ .base = 0xe900 },
15147fec6515SAnjelique Melendez 	},
15157fec6515SAnjelique Melendez };
15167fec6515SAnjelique Melendez 
151724e2d05dSBjorn Andersson static const struct of_device_id lpg_of_table[] = {
151824e2d05dSBjorn Andersson 	{ .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data },
151924e2d05dSBjorn Andersson 	{ .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data },
152096c59c8bSSatya Priya 	{ .compatible = "qcom,pm8350c-pwm", .data = &pm8350c_pwm_data },
152124e2d05dSBjorn Andersson 	{ .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data },
152224e2d05dSBjorn Andersson 	{ .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data },
152324e2d05dSBjorn Andersson 	{ .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data },
1524d11a79ddSLuca Weiss 	{ .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data },
152524e2d05dSBjorn Andersson 	{ .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
152624e2d05dSBjorn Andersson 	{ .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
152724e2d05dSBjorn Andersson 	{ .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },
15287fec6515SAnjelique Melendez 	{ .compatible = "qcom,pmk8550-pwm", .data = &pmk8550_pwm_data },
152924e2d05dSBjorn Andersson 	{}
153024e2d05dSBjorn Andersson };
153124e2d05dSBjorn Andersson MODULE_DEVICE_TABLE(of, lpg_of_table);
153224e2d05dSBjorn Andersson 
153324e2d05dSBjorn Andersson static struct platform_driver lpg_driver = {
153424e2d05dSBjorn Andersson 	.probe = lpg_probe,
153524e2d05dSBjorn Andersson 	.remove = lpg_remove,
153624e2d05dSBjorn Andersson 	.driver = {
153724e2d05dSBjorn Andersson 		.name = "qcom-spmi-lpg",
153824e2d05dSBjorn Andersson 		.of_match_table = lpg_of_table,
153924e2d05dSBjorn Andersson 	},
154024e2d05dSBjorn Andersson };
154124e2d05dSBjorn Andersson module_platform_driver(lpg_driver);
154224e2d05dSBjorn Andersson 
154324e2d05dSBjorn Andersson MODULE_DESCRIPTION("Qualcomm LPG LED driver");
154424e2d05dSBjorn Andersson MODULE_LICENSE("GPL v2");
1545