// SPDX-License-Identifier: GPL-2.0-or-later /* * LED driver for Mediatek MT6323 PMIC * * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>
*/ #include <linux/kernel.h> #include <linux/leds.h> #include <linux/mfd/mt6323/registers.h> #include <linux/mfd/mt6397/core.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/regmap.h>
/* * Register field for TOP_CKPDN0 to enable * 32K clock common for LED device.
*/ #define RG_DRV_32K_CK_PDN BIT(11) #define RG_DRV_32K_CK_PDN_MASK BIT(11)
/* 32K/1M/6M clock common for WLED device */ #define RG_VWLED_1M_CK_PDN BIT(0) #define RG_VWLED_32K_CK_PDN BIT(12) #define RG_VWLED_6M_CK_PDN BIT(13)
/* * Register field for TOP_CKPDN2 to enable * individual clock for LED device.
*/ #define RG_ISINK_CK_PDN(i) BIT(i) #define RG_ISINK_CK_PDN_MASK(i) BIT(i)
/* * Register field for TOP_CKCON1 to select * clock source.
*/ #define RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))
#define ISINK_CON(r, i) (r + 0x8 * (i))
/* ISINK_CON0: Register to setup the duty cycle of the blink. */ #define ISINK_DIM_DUTY_MASK (0x1f << 8) #define ISINK_DIM_DUTY(i) (((i) << 8) & ISINK_DIM_DUTY_MASK)
/* ISINK_CON1: Register to setup the period of the blink. */ #define ISINK_DIM_FSEL_MASK (0xffff) #define ISINK_DIM_FSEL(i) ((i) & ISINK_DIM_FSEL_MASK)
/* Register to LED channel enablement. */ #define ISINK_CH_EN_MASK(i) BIT(i) #define ISINK_CH_EN(i) BIT(i)
#define MAX_SUPPORTED_LEDS 8
struct mt6323_leds;
/** * struct mt6323_led - state container for the LED device * @id: the identifier in MT6323 LED device * @parent: the pointer to MT6323 LED controller * @cdev: LED class device for this LED device * @current_brightness: current state of the LED device
*/ struct mt6323_led { int id; struct mt6323_leds *parent; struct led_classdev cdev; enum led_brightness current_brightness;
};
/** * struct mt6323_regs - register spec for the LED device * @top_ckpdn: Offset to ISINK_CKPDN[0..x] registers * @num_top_ckpdn: Number of ISINK_CKPDN registers * @top_ckcon: Offset to ISINK_CKCON[0..x] registers * @num_top_ckcon: Number of ISINK_CKCON registers * @isink_con: Offset to ISINKx_CON[0..x] registers * @num_isink_con: Number of ISINKx_CON registers * @isink_max_regs: Number of ISINK[0..x] registers * @isink_en_ctrl: Offset to ISINK_EN_CTRL register * @iwled_en_ctrl: Offset to IWLED_EN_CTRL register
*/ struct mt6323_regs { const u16 *top_ckpdn;
u8 num_top_ckpdn; const u16 *top_ckcon;
u8 num_top_ckcon; const u16 *isink_con;
u8 num_isink_con;
u8 isink_max_regs;
u16 isink_en_ctrl;
u16 iwled_en_ctrl;
};
/** * struct mt6323_hwspec - hardware specific parameters * @max_period: Maximum period for all LEDs * @max_leds: Maximum number of supported LEDs * @max_wleds: Maximum number of WLEDs * @max_brightness: Maximum brightness for all LEDs * @unit_duty: Steps of duty per period
*/ struct mt6323_hwspec {
u16 max_period;
u8 max_leds;
u8 max_wleds;
u16 max_brightness;
u16 unit_duty;
};
/** * struct mt6323_data - device specific data * @regs: Register spec for this device * @spec: Hardware specific parameters
*/ struct mt6323_data { conststruct mt6323_regs *regs; conststruct mt6323_hwspec *spec;
};
/** * struct mt6323_leds - state container for holding LED controller * of the driver * @dev: the device pointer * @hw: the underlying hardware providing shared * bus for the register operations * @pdata: device specific data * @lock: the lock among process context * @led: the array that contains the state of individual * LED device
*/ struct mt6323_leds { struct device *dev; struct mt6397_chip *hw; conststruct mt6323_data *pdata; /* protect among process context */ struct mutex lock; struct mt6323_led *led[MAX_SUPPORTED_LEDS];
};
/* * Setup required clock source, enable the corresponding * clock and channel and let work with continuous blink as * the default.
*/
ret = regmap_update_bits(regmap, regs->top_ckcon[1],
RG_ISINK_CK_SEL_MASK(led->id), 0); if (ret < 0) return ret;
status = RG_ISINK_CK_PDN(led->id);
ret = regmap_update_bits(regmap, regs->top_ckpdn[2],
RG_ISINK_CK_PDN_MASK(led->id),
~status); if (ret < 0) return ret;
usleep_range(100, 300);
ret = regmap_update_bits(regmap, regs->isink_en_ctrl,
ISINK_CH_EN_MASK(led->id),
ISINK_CH_EN(led->id)); if (ret < 0) return ret;
ret = mt6323_led_hw_brightness(cdev, brightness); if (ret < 0) return ret;
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
ISINK_DIM_DUTY_MASK,
ISINK_DIM_DUTY(31)); if (ret < 0) return ret;
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),
ISINK_DIM_FSEL_MASK,
ISINK_DIM_FSEL(1000)); if (ret < 0) return ret;
/* * LED subsystem requires a default user * friendly blink pattern for the LED so using * 1Hz duty cycle 50% here if without specific * value delay_on and delay off being assigned.
*/ if (!*delay_on && !*delay_off) {
*delay_on = 500;
*delay_off = 500;
}
/* * Units are in ms, if over the hardware able * to support, fallback into software blink
*/
period = *delay_on + *delay_off;
if (period > spec->max_period) return -EINVAL;
/* * Calculate duty_hw based on the percentage of period during * which the led is ON.
*/
duty_hw = DIV_ROUND_CLOSEST(*delay_on * 100000ul, period * spec->unit_duty);
/* hardware doesn't support zero duty cycle. */ if (!duty_hw) return -EINVAL;
mutex_lock(&leds->lock); /* * Set max_brightness as the software blink behavior * when no blink brightness.
*/ if (!led->current_brightness) {
ret = mt6323_led_hw_on(cdev, cdev->max_brightness); if (ret < 0) goto out;
led->current_brightness = cdev->max_brightness;
}
ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
ISINK_DIM_DUTY_MASK,
ISINK_DIM_DUTY(duty_hw - 1)); if (ret < 0) goto out;
if (brightness) { if (!led->current_brightness)
ret = mtk_wled_hw_on(cdev); if (ret) goto out;
} else {
ret = mtk_wled_hw_off(cdev); if (ret) goto out;
}
state = led_init_default_state_get(of_fwnode_handle(np)); switch (state) { case LEDS_DEFSTATE_ON:
ret = mt6323_led_set_brightness(cdev, cdev->max_brightness); break; case LEDS_DEFSTATE_KEEP:
ret = mt6323_get_led_hw_brightness(cdev); if (ret < 0) return ret;
led->current_brightness = ret;
ret = 0; break; default:
ret = mt6323_led_set_brightness(cdev, LED_OFF);
}
ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child); if (ret < 0) {
dev_err(leds->dev, "Failed to LED set default from devicetree\n"); return ret;
}
init_data.fwnode = of_fwnode_handle(child);
ret = devm_led_classdev_register_ext(dev, &leds->led[reg]->cdev,
&init_data); if (ret) {
dev_err(dev, "Failed to register LED: %d\n", ret); return ret;
}
}
staticconststruct mt6323_hwspec mt6332_spec = { /* There are no LEDs in MT6332. Only WLEDs are present. */
.max_leds = 0,
.max_wleds = 1,
.max_brightness = 1024,
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.