259 lines
8.0 KiB
C
259 lines
8.0 KiB
C
// 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");
|