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", ®);
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