// 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 #include #include #include #include #include #include #include #include #include #include #include #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 hard reset configuration */ #define QPNP_PON_REVISION2 0x0801U #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 struct sys_off_handler *pon_poweroff_handler; static struct sys_off_handler *pon_restart_handler; 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 void msm8905_pon_config_reset(unsigned int rst_type) { unsigned int rev2 = 0; unsigned int ctl_reg = QPNP_PON_PS_HOLD_RST_CTL2; if (!pon_regmap) return; if (regmap_read(pon_regmap, QPNP_PON_REVISION2, &rev2) == 0 && rev2 == 0) ctl_reg = QPNP_PON_PS_HOLD_RST_CTL; /* * Match Qualcomm qpnp_pon_system_pwr_off(): * 1. Disable S2_CNTL_EN (bit 7 of 0x085b on Gen2 / 0x085a on Gen1) * 2. Wait >10 sleep clock cycles (~350 us) * 3. Program PS_HOLD_RST_CTL (0x085a) to SHUTDOWN (0x04) or HARD_RESET (0x07) * 4. Re-enable S2_CNTL_EN (bit 7) and wait ~350 us */ regmap_update_bits(pon_regmap, ctl_reg, QPNP_PON_S2_CNTL_EN, 0); udelay(350); regmap_update_bits(pon_regmap, QPNP_PON_PS_HOLD_RST_CTL, QPNP_PON_RST_TYPE_MASK, rst_type); udelay(350); regmap_update_bits(pon_regmap, ctl_reg, QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN); udelay(350); pr_info("leds-msm8905-torch: configured PM8909 PON PS_HOLD_RST_CTL=0x%02x (%s)\n", rst_type, (rst_type == PON_POWER_OFF_SHUTDOWN) ? "shutdown" : "hard-reset"); } static int msm8905_pon_sys_poweroff(struct sys_off_data *data) { msm8905_torch_hw_disable(); msm8905_pon_config_reset(PON_POWER_OFF_SHUTDOWN); return NOTIFY_DONE; } static int msm8905_pon_sys_restart(struct sys_off_data *data) { msm8905_torch_hw_disable(); msm8905_pon_config_reset(PON_POWER_OFF_HARD_RESET); return NOTIFY_DONE; } static int msm8905_pon_reboot_notify(struct notifier_block *nb, unsigned long action, void *data) { unsigned int rst_type; msm8905_torch_hw_disable(); if (action == SYS_POWER_OFF || action == SYS_HALT) rst_type = PON_POWER_OFF_SHUTDOWN; else rst_type = PON_POWER_OFF_HARD_RESET; msm8905_pon_config_reset(rst_type); 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; struct platform_device *codec_pdev; struct regmap *codec_regmap = NULL; np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-pon"); if (np) { pon_pdev = of_find_device_by_node(np); of_node_put(np); if (pon_pdev) { if (pon_pdev->dev.parent) pon_regmap = dev_get_regmap(pon_pdev->dev.parent, NULL); put_device(&pon_pdev->dev); } } /* * Configure PM8909 WCD analog codec (USID 1): * - CDC_A_MICB_1_VAL (0xf141) = 0x60 (2.8V micbias for internal MEMS mic) * - CDC_A_RX_EAR_CTL (0xf1c0) |= 0x20 (+6 dB EAR PA gain for earpiece) */ np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-wcd-analog-codec"); if (np) { codec_pdev = of_find_device_by_node(np); of_node_put(np); if (codec_pdev) { if (codec_pdev->dev.parent) codec_regmap = dev_get_regmap(codec_pdev->dev.parent, NULL); put_device(&codec_pdev->dev); } } if (codec_regmap) { regmap_write(codec_regmap, 0xf141U, 0x60U); regmap_update_bits(codec_regmap, 0xf1c0U, 0x20U, 0x20U); } } 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(); msm8905_pon_config_reset(PON_POWER_OFF_HARD_RESET); register_reboot_notifier(&msm8905_pon_reboot_nb); pon_poweroff_handler = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, msm8905_pon_sys_poweroff, NULL); pon_restart_handler = register_sys_off_handler(SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH, msm8905_pon_sys_restart, NULL); 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) { if (!IS_ERR_OR_NULL(pon_restart_handler)) unregister_sys_off_handler(pon_restart_handler); if (!IS_ERR_OR_NULL(pon_poweroff_handler)) unregister_sys_off_handler(pon_poweroff_handler); 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");