Files
bananarchy/src/leds-msm8905-torch/leds-msm8905-torch.c
T

259 lines
8.0 KiB
C
Raw Normal View History

2026-10-19 09:23:54 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Qualcomm MSM8905 / Nokia 8110 4G (nokia-argon) SY7807 Rear Flashlight Driver
*
* Drives the SY7807 boost LED controller on TLMM GPIO_32 (cci_timer1) using
* GCC camss_gp1_clk_src at 150 kHz (50% duty cycle in continuous Torch mode),
* matching the hardware sequence from the stock Nokia 8110 4G kernel.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>
#define MSM8905_TLMM_GPIO32_PHYS 0x01020000UL
#define MSM8905_TLMM_GPIO32_SIZE 0x1000UL
#define GPIO32_CFG_REG 0x0000U
#define GPIO32_CFG_CCI_TIMER1 0x0008U /* 2mA, no pull, func=2 (cci_timer1) */
#define GPIO32_CFG_GPIO_OFF 0x0001U /* 2mA, pull-down, func=0 (gpio in) */
#define MSM8905_GCC_CAMSS_PHYS 0x01850000UL
#define MSM8905_GCC_CAMSS_SIZE 0x6000UL
#define GCC_CAMSS_GP1_CBCR_STOCK 0x0004U /* Stock MSM8909 gcc_camss_gp1_clk CBCR */
#define GCC_CAMSS_GP1_CMD_RCGR 0x5000U /* camss_gp1_clk_src CMD_RCGR */
#define GCC_CAMSS_GP1_CFG_RCGR 0x5004U /* camss_gp1_clk_src CFG_RCGR */
#define GCC_CAMSS_GP1_M_REG 0x5008U /* M = 1 */
#define GCC_CAMSS_GP1_N_REG 0x500cU /* ~(128 - 1) = 0xff80 (19.2MHz / 128 = 150kHz) */
#define GCC_CAMSS_GP1_D_REG 0x5010U /* ~128 = 0xff7f (50% duty cycle) */
#define GCC_CAMSS_GP1_CBCR_MAIN 0x5018U /* Mainline gcc_camss_gp1_clk CBCR */
#define RCGR_CFG_150KHZ_XO 0x2001U /* Dual-edge M/N:D (mode=2), XO (src=0), div=1 */
#define RCGR_M_150KHZ 0x0001U
#define RCGR_N_150KHZ 0xff80U
#define RCGR_D_TORCH_50PCT 0xff7fU
#define RCGR_CMD_ENABLE_UPDATE 0x0003U /* ROOT_EN | CMD_UPDATE */
/* PM8909 PON registers for PS_HOLD shutdown vs warm reset configuration */
#define QPNP_PON_PS_HOLD_RST_CTL 0x085aU
#define QPNP_PON_PS_HOLD_RST_CTL2 0x085bU
#define QPNP_PON_S2_CNTL_EN 0x80U
#define QPNP_PON_RST_TYPE_MASK 0x0fU
#define PON_POWER_OFF_WARM_RESET 0x01U
#define PON_POWER_OFF_SHUTDOWN 0x04U
#define PON_POWER_OFF_HARD_RESET 0x07U
static void __iomem *tlmm_gpio32_base;
static void __iomem *gcc_camss_base;
static struct regmap *pon_regmap;
static DEFINE_SPINLOCK(torch_lock);
static enum led_brightness current_brightness = LED_OFF;
static void msm8905_torch_hw_enable(enum led_brightness value)
{
unsigned long flags;
u32 d_count;
u32 d_reg;
/*
* Scale PWM duty cycle up to 50% (d_count = 64 of N = 128) at max brightness
* so the SY7807 remains in continuous Torch mode without triggering its
* high-current Flash Strobe safety timeout.
*/
d_count = (u32)value * 64U / 255U;
if (d_count < 8U)
d_count = 8U;
if (d_count > 64U)
d_count = 64U;
d_reg = (~(2U * d_count)) & 0xffffU;
spin_lock_irqsave(&torch_lock, flags);
writel_relaxed(RCGR_CFG_150KHZ_XO, gcc_camss_base + GCC_CAMSS_GP1_CFG_RCGR);
writel_relaxed(RCGR_M_150KHZ, gcc_camss_base + GCC_CAMSS_GP1_M_REG);
writel_relaxed(RCGR_N_150KHZ, gcc_camss_base + GCC_CAMSS_GP1_N_REG);
writel_relaxed(d_reg, gcc_camss_base + GCC_CAMSS_GP1_D_REG);
writel_relaxed(RCGR_CMD_ENABLE_UPDATE, gcc_camss_base + GCC_CAMSS_GP1_CMD_RCGR);
wmb();
writel_relaxed(0x1U, gcc_camss_base + GCC_CAMSS_GP1_CBCR_STOCK);
writel_relaxed(0x1U, gcc_camss_base + GCC_CAMSS_GP1_CBCR_MAIN);
writel_relaxed(GPIO32_CFG_CCI_TIMER1, tlmm_gpio32_base + GPIO32_CFG_REG);
wmb();
current_brightness = value;
spin_unlock_irqrestore(&torch_lock, flags);
}
static void msm8905_torch_hw_disable(void)
{
unsigned long flags;
spin_lock_irqsave(&torch_lock, flags);
writel_relaxed(GPIO32_CFG_GPIO_OFF, tlmm_gpio32_base + GPIO32_CFG_REG);
writel_relaxed(0x0U, gcc_camss_base + GCC_CAMSS_GP1_CBCR_STOCK);
writel_relaxed(0x0U, gcc_camss_base + GCC_CAMSS_GP1_CBCR_MAIN);
writel_relaxed(0x0U, gcc_camss_base + GCC_CAMSS_GP1_CMD_RCGR);
wmb();
current_brightness = LED_OFF;
spin_unlock_irqrestore(&torch_lock, flags);
}
static void msm8905_torch_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value == LED_OFF)
msm8905_torch_hw_disable();
else
msm8905_torch_hw_enable(value);
}
static enum led_brightness msm8905_torch_brightness_get(struct led_classdev *led_cdev)
{
return current_brightness;
}
static struct led_classdev msm8905_torch_led = {
.name = "white:torch",
.max_brightness = LED_FULL,
.brightness_set = msm8905_torch_brightness_set,
.brightness_get = msm8905_torch_brightness_get,
.flags = LED_CORE_SUSPENDRESUME,
};
static int msm8905_pon_reboot_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
unsigned int ctl1 = 0;
unsigned int ctl2 = 0;
unsigned int rst_type;
msm8905_torch_hw_disable();
if (!pon_regmap)
return NOTIFY_DONE;
if (action == SYS_POWER_OFF || action == SYS_HALT)
rst_type = PON_POWER_OFF_SHUTDOWN;
else
rst_type = PON_POWER_OFF_WARM_RESET;
/*
* Match Qualcomm qpnp_pon_system_pwr_off():
* 1. Disable S2_CNTL_EN (bit 7 of 0x085b)
* 2. Wait >10 sleep clock cycles (~500 us)
* 3. Program PS_HOLD_RST_CTL (0x085a) to SHUTDOWN (0x04) or WARM_RESET (0x01)
* 4. Re-enable S2_CNTL_EN (bit 7 of 0x085b) and wait ~500 us
*/
if (regmap_read(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL2, &ctl2) == 0)
regmap_write(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL2, ctl2 & ~QPNP_PON_S2_CNTL_EN);
else
regmap_write(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL2, 0x00U);
udelay(500);
if (regmap_read(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL, &ctl1) == 0)
ctl1 = (ctl1 & ~QPNP_PON_RST_TYPE_MASK) | rst_type;
else
ctl1 = rst_type;
regmap_write(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL, ctl1);
udelay(500);
regmap_write(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL2,
(ctl2 & ~QPNP_PON_S2_CNTL_EN) | QPNP_PON_S2_CNTL_EN);
udelay(500);
pr_info("leds-msm8905-torch: configured PM8909 PON PS_HOLD_RST_CTL=0x%02x for %s\n",
ctl1, (rst_type == PON_POWER_OFF_SHUTDOWN) ? "shutdown" : "reboot");
return NOTIFY_OK;
}
static struct notifier_block msm8905_pon_reboot_nb = {
.notifier_call = msm8905_pon_reboot_notify,
.priority = 128,
};
static void msm8905_pon_lookup_regmap(void)
{
struct device_node *np;
struct platform_device *pon_pdev;
np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-pon");
if (!np)
return;
pon_pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pon_pdev)
return;
if (pon_pdev->dev.parent)
pon_regmap = dev_get_regmap(pon_pdev->dev.parent, NULL);
put_device(&pon_pdev->dev);
}
static int __init msm8905_torch_init(void)
{
int ret;
if (!of_machine_is_compatible("qcom,msm8905") &&
!of_machine_is_compatible("qcom,msm8909") &&
!of_machine_is_compatible("nokia,argon")) {
return -ENODEV;
}
tlmm_gpio32_base = ioremap(MSM8905_TLMM_GPIO32_PHYS, MSM8905_TLMM_GPIO32_SIZE);
if (!tlmm_gpio32_base)
return -ENOMEM;
gcc_camss_base = ioremap(MSM8905_GCC_CAMSS_PHYS, MSM8905_GCC_CAMSS_SIZE);
if (!gcc_camss_base) {
iounmap(tlmm_gpio32_base);
return -ENOMEM;
}
msm8905_torch_hw_disable();
ret = led_classdev_register(NULL, &msm8905_torch_led);
if (ret) {
iounmap(gcc_camss_base);
iounmap(tlmm_gpio32_base);
return ret;
}
msm8905_pon_lookup_regmap();
register_reboot_notifier(&msm8905_pon_reboot_nb);
pr_info("leds-msm8905-torch: registered white:torch on GPIO_32 (cci_timer1 150kHz PWM), pon_regmap=%s\n",
pon_regmap ? "ready" : "none");
return 0;
}
static void __exit msm8905_torch_exit(void)
{
unregister_reboot_notifier(&msm8905_pon_reboot_nb);
led_classdev_unregister(&msm8905_torch_led);
msm8905_torch_hw_disable();
iounmap(gcc_camss_base);
iounmap(tlmm_gpio32_base);
}
module_init(msm8905_torch_init);
module_exit(msm8905_torch_exit);
MODULE_DESCRIPTION("Qualcomm MSM8905 / Nokia 8110 4G SY7807 Torch LED & PM8909 PON Shutdown driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-msm8905-torch");