1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Backlight driver for Maxim MAX8925 4 * 5 * Copyright (C) 2009 Marvell International Ltd. 6 * Haojian Zhuang <haojian.zhuang@marvell.com> 7 */ 8 9 #include <linux/init.h> 10 #include <linux/kernel.h> 11 #include <linux/platform_device.h> 12 #include <linux/fb.h> 13 #include <linux/i2c.h> 14 #include <linux/backlight.h> 15 #include <linux/mfd/max8925.h> 16 #include <linux/slab.h> 17 #include <linux/module.h> 18 19 #define MAX_BRIGHTNESS (0xff) 20 #define MIN_BRIGHTNESS (0) 21 22 #define LWX_FREQ(x) (((x - 601) / 100) & 0x7) 23 24 struct max8925_backlight_data { 25 struct max8925_chip *chip; 26 27 int current_brightness; 28 int reg_mode_cntl; 29 int reg_cntl; 30 }; 31 32 static int max8925_backlight_set(struct backlight_device *bl, int brightness) 33 { 34 struct max8925_backlight_data *data = bl_get_data(bl); 35 struct max8925_chip *chip = data->chip; 36 unsigned char value; 37 int ret; 38 39 if (brightness > MAX_BRIGHTNESS) 40 value = MAX_BRIGHTNESS; 41 else 42 value = brightness; 43 44 ret = max8925_reg_write(chip->i2c, data->reg_cntl, value); 45 if (ret < 0) 46 goto out; 47 48 if (!data->current_brightness && brightness) 49 /* enable WLED output */ 50 ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 1); 51 else if (!brightness) 52 /* disable WLED output */ 53 ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 0); 54 if (ret < 0) 55 goto out; 56 dev_dbg(chip->dev, "set brightness %d\n", value); 57 data->current_brightness = value; 58 return 0; 59 out: 60 dev_dbg(chip->dev, "set brightness %d failure with return value:%d\n", 61 value, ret); 62 return ret; 63 } 64 65 static int max8925_backlight_update_status(struct backlight_device *bl) 66 { 67 int brightness = bl->props.brightness; 68 69 if (bl->props.power != FB_BLANK_UNBLANK) 70 brightness = 0; 71 72 if (bl->props.fb_blank != FB_BLANK_UNBLANK) 73 brightness = 0; 74 75 if (bl->props.state & BL_CORE_SUSPENDED) 76 brightness = 0; 77 78 return max8925_backlight_set(bl, brightness); 79 } 80 81 static int max8925_backlight_get_brightness(struct backlight_device *bl) 82 { 83 struct max8925_backlight_data *data = bl_get_data(bl); 84 struct max8925_chip *chip = data->chip; 85 int ret; 86 87 ret = max8925_reg_read(chip->i2c, data->reg_cntl); 88 if (ret < 0) 89 return -EINVAL; 90 data->current_brightness = ret; 91 dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); 92 return ret; 93 } 94 95 static const struct backlight_ops max8925_backlight_ops = { 96 .options = BL_CORE_SUSPENDRESUME, 97 .update_status = max8925_backlight_update_status, 98 .get_brightness = max8925_backlight_get_brightness, 99 }; 100 101 static void max8925_backlight_dt_init(struct platform_device *pdev) 102 { 103 struct device_node *nproot = pdev->dev.parent->of_node, *np; 104 struct max8925_backlight_pdata *pdata; 105 u32 val; 106 107 if (!nproot || !IS_ENABLED(CONFIG_OF)) 108 return; 109 110 pdata = devm_kzalloc(&pdev->dev, 111 sizeof(struct max8925_backlight_pdata), 112 GFP_KERNEL); 113 if (!pdata) 114 return; 115 116 np = of_get_child_by_name(nproot, "backlight"); 117 if (!np) { 118 dev_err(&pdev->dev, "failed to find backlight node\n"); 119 return; 120 } 121 122 if (!of_property_read_u32(np, "maxim,max8925-dual-string", &val)) 123 pdata->dual_string = val; 124 125 of_node_put(np); 126 127 pdev->dev.platform_data = pdata; 128 } 129 130 static int max8925_backlight_probe(struct platform_device *pdev) 131 { 132 struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); 133 struct max8925_backlight_pdata *pdata; 134 struct max8925_backlight_data *data; 135 struct backlight_device *bl; 136 struct backlight_properties props; 137 struct resource *res; 138 unsigned char value; 139 int ret = 0; 140 141 data = devm_kzalloc(&pdev->dev, sizeof(struct max8925_backlight_data), 142 GFP_KERNEL); 143 if (data == NULL) 144 return -ENOMEM; 145 146 res = platform_get_resource(pdev, IORESOURCE_REG, 0); 147 if (!res) { 148 dev_err(&pdev->dev, "No REG resource for mode control!\n"); 149 return -ENXIO; 150 } 151 data->reg_mode_cntl = res->start; 152 res = platform_get_resource(pdev, IORESOURCE_REG, 1); 153 if (!res) { 154 dev_err(&pdev->dev, "No REG resource for control!\n"); 155 return -ENXIO; 156 } 157 data->reg_cntl = res->start; 158 159 data->chip = chip; 160 data->current_brightness = 0; 161 162 memset(&props, 0, sizeof(struct backlight_properties)); 163 props.type = BACKLIGHT_RAW; 164 props.max_brightness = MAX_BRIGHTNESS; 165 bl = devm_backlight_device_register(&pdev->dev, "max8925-backlight", 166 &pdev->dev, data, 167 &max8925_backlight_ops, &props); 168 if (IS_ERR(bl)) { 169 dev_err(&pdev->dev, "failed to register backlight\n"); 170 return PTR_ERR(bl); 171 } 172 bl->props.brightness = MAX_BRIGHTNESS; 173 174 platform_set_drvdata(pdev, bl); 175 176 value = 0; 177 if (!pdev->dev.platform_data) 178 max8925_backlight_dt_init(pdev); 179 180 pdata = pdev->dev.platform_data; 181 if (pdata) { 182 if (pdata->lxw_scl) 183 value |= (1 << 7); 184 if (pdata->lxw_freq) 185 value |= (LWX_FREQ(pdata->lxw_freq) << 4); 186 if (pdata->dual_string) 187 value |= (1 << 1); 188 } 189 ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 0xfe, value); 190 if (ret < 0) 191 return ret; 192 backlight_update_status(bl); 193 return 0; 194 } 195 196 static struct platform_driver max8925_backlight_driver = { 197 .driver = { 198 .name = "max8925-backlight", 199 }, 200 .probe = max8925_backlight_probe, 201 }; 202 203 module_platform_driver(max8925_backlight_driver); 204 205 MODULE_DESCRIPTION("Backlight Driver for Maxim MAX8925"); 206 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 207 MODULE_LICENSE("GPL"); 208 MODULE_ALIAS("platform:max8925-backlight"); 209