1*b516456eSPetri Karhula // SPDX-License-Identifier: GPL-2.0-only 2*b516456eSPetri Karhula /* 3*b516456eSPetri Karhula * Congatec Board Controller (CGBC) Backlight Driver 4*b516456eSPetri Karhula * 5*b516456eSPetri Karhula * This driver provides backlight control for LCD displays connected to 6*b516456eSPetri Karhula * Congatec boards via the CGBC (Congatec Board Controller). It integrates 7*b516456eSPetri Karhula * with the Linux backlight subsystem and communicates with hardware through 8*b516456eSPetri Karhula * the cgbc-core module. 9*b516456eSPetri Karhula * 10*b516456eSPetri Karhula * Copyright (C) 2025 Novatron Oy 11*b516456eSPetri Karhula * 12*b516456eSPetri Karhula * Author: Petri Karhula <petri.karhula@novatron.fi> 13*b516456eSPetri Karhula */ 14*b516456eSPetri Karhula 15*b516456eSPetri Karhula #include <linux/backlight.h> 16*b516456eSPetri Karhula #include <linux/bitfield.h> 17*b516456eSPetri Karhula #include <linux/bits.h> 18*b516456eSPetri Karhula #include <linux/mfd/cgbc.h> 19*b516456eSPetri Karhula #include <linux/module.h> 20*b516456eSPetri Karhula #include <linux/platform_device.h> 21*b516456eSPetri Karhula 22*b516456eSPetri Karhula #define BLT_PWM_DUTY_MASK GENMASK(6, 0) 23*b516456eSPetri Karhula 24*b516456eSPetri Karhula /* CGBC command for PWM brightness control*/ 25*b516456eSPetri Karhula #define CGBC_CMD_BLT0_PWM 0x75 26*b516456eSPetri Karhula 27*b516456eSPetri Karhula #define CGBC_BL_MAX_BRIGHTNESS 100 28*b516456eSPetri Karhula 29*b516456eSPetri Karhula /** 30*b516456eSPetri Karhula * CGBC backlight driver data 31*b516456eSPetri Karhula * @dev: Pointer to the platform device 32*b516456eSPetri Karhula * @cgbc: Pointer to the parent CGBC device data 33*b516456eSPetri Karhula * @current_brightness: Current brightness level (0-100) 34*b516456eSPetri Karhula */ 35*b516456eSPetri Karhula struct cgbc_bl_data { 36*b516456eSPetri Karhula struct device *dev; 37*b516456eSPetri Karhula struct cgbc_device_data *cgbc; 38*b516456eSPetri Karhula unsigned int current_brightness; 39*b516456eSPetri Karhula }; 40*b516456eSPetri Karhula 41*b516456eSPetri Karhula static int cgbc_bl_read_brightness(struct cgbc_bl_data *bl_data) 42*b516456eSPetri Karhula { 43*b516456eSPetri Karhula u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM }; 44*b516456eSPetri Karhula u8 reply_buf[3]; 45*b516456eSPetri Karhula int ret; 46*b516456eSPetri Karhula 47*b516456eSPetri Karhula ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), 48*b516456eSPetri Karhula reply_buf, sizeof(reply_buf), NULL); 49*b516456eSPetri Karhula if (ret < 0) 50*b516456eSPetri Karhula return ret; 51*b516456eSPetri Karhula 52*b516456eSPetri Karhula /* 53*b516456eSPetri Karhula * Get only PWM duty factor percentage, 54*b516456eSPetri Karhula * ignore polarity inversion bit (bit 7) 55*b516456eSPetri Karhula */ 56*b516456eSPetri Karhula bl_data->current_brightness = FIELD_GET(BLT_PWM_DUTY_MASK, reply_buf[0]); 57*b516456eSPetri Karhula 58*b516456eSPetri Karhula return 0; 59*b516456eSPetri Karhula } 60*b516456eSPetri Karhula 61*b516456eSPetri Karhula static int cgbc_bl_update_status(struct backlight_device *bl) 62*b516456eSPetri Karhula { 63*b516456eSPetri Karhula struct cgbc_bl_data *bl_data = bl_get_data(bl); 64*b516456eSPetri Karhula u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM }; 65*b516456eSPetri Karhula u8 reply_buf[3]; 66*b516456eSPetri Karhula u8 brightness; 67*b516456eSPetri Karhula int ret; 68*b516456eSPetri Karhula 69*b516456eSPetri Karhula brightness = backlight_get_brightness(bl); 70*b516456eSPetri Karhula 71*b516456eSPetri Karhula if (brightness != bl_data->current_brightness) { 72*b516456eSPetri Karhula /* Read the current values */ 73*b516456eSPetri Karhula ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf, 74*b516456eSPetri Karhula sizeof(reply_buf), NULL); 75*b516456eSPetri Karhula if (ret < 0) { 76*b516456eSPetri Karhula dev_err(bl_data->dev, "Failed to read PWM settings: %d\n", ret); 77*b516456eSPetri Karhula return ret; 78*b516456eSPetri Karhula } 79*b516456eSPetri Karhula 80*b516456eSPetri Karhula /* 81*b516456eSPetri Karhula * Prepare command buffer for writing new settings. Only 2nd byte is changed 82*b516456eSPetri Karhula * to set new brightness (PWM duty cycle %). Other values (polarity, frequency) 83*b516456eSPetri Karhula * are preserved from the read values. 84*b516456eSPetri Karhula */ 85*b516456eSPetri Karhula cmd_buf[1] = (reply_buf[0] & ~BLT_PWM_DUTY_MASK) | 86*b516456eSPetri Karhula FIELD_PREP(BLT_PWM_DUTY_MASK, brightness); 87*b516456eSPetri Karhula cmd_buf[2] = reply_buf[1]; 88*b516456eSPetri Karhula cmd_buf[3] = reply_buf[2]; 89*b516456eSPetri Karhula 90*b516456eSPetri Karhula ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf, 91*b516456eSPetri Karhula sizeof(reply_buf), NULL); 92*b516456eSPetri Karhula if (ret < 0) { 93*b516456eSPetri Karhula dev_err(bl_data->dev, "Failed to set brightness: %d\n", ret); 94*b516456eSPetri Karhula return ret; 95*b516456eSPetri Karhula } 96*b516456eSPetri Karhula 97*b516456eSPetri Karhula bl_data->current_brightness = reply_buf[0] & BLT_PWM_DUTY_MASK; 98*b516456eSPetri Karhula 99*b516456eSPetri Karhula /* Verify the setting was applied correctly */ 100*b516456eSPetri Karhula if (bl_data->current_brightness != brightness) { 101*b516456eSPetri Karhula dev_err(bl_data->dev, 102*b516456eSPetri Karhula "Brightness setting verification failed (got %u, expected %u)\n", 103*b516456eSPetri Karhula bl_data->current_brightness, (unsigned int)brightness); 104*b516456eSPetri Karhula return -EIO; 105*b516456eSPetri Karhula } 106*b516456eSPetri Karhula } 107*b516456eSPetri Karhula 108*b516456eSPetri Karhula return 0; 109*b516456eSPetri Karhula } 110*b516456eSPetri Karhula 111*b516456eSPetri Karhula static int cgbc_bl_get_brightness(struct backlight_device *bl) 112*b516456eSPetri Karhula { 113*b516456eSPetri Karhula struct cgbc_bl_data *bl_data = bl_get_data(bl); 114*b516456eSPetri Karhula int ret; 115*b516456eSPetri Karhula 116*b516456eSPetri Karhula ret = cgbc_bl_read_brightness(bl_data); 117*b516456eSPetri Karhula if (ret < 0) { 118*b516456eSPetri Karhula dev_err(bl_data->dev, "Failed to read brightness: %d\n", ret); 119*b516456eSPetri Karhula return ret; 120*b516456eSPetri Karhula } 121*b516456eSPetri Karhula 122*b516456eSPetri Karhula return bl_data->current_brightness; 123*b516456eSPetri Karhula } 124*b516456eSPetri Karhula 125*b516456eSPetri Karhula static const struct backlight_ops cgbc_bl_ops = { 126*b516456eSPetri Karhula .options = BL_CORE_SUSPENDRESUME, 127*b516456eSPetri Karhula .update_status = cgbc_bl_update_status, 128*b516456eSPetri Karhula .get_brightness = cgbc_bl_get_brightness, 129*b516456eSPetri Karhula }; 130*b516456eSPetri Karhula 131*b516456eSPetri Karhula static int cgbc_bl_probe(struct platform_device *pdev) 132*b516456eSPetri Karhula { 133*b516456eSPetri Karhula struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent); 134*b516456eSPetri Karhula struct backlight_properties props = { }; 135*b516456eSPetri Karhula struct backlight_device *bl_dev; 136*b516456eSPetri Karhula struct cgbc_bl_data *bl_data; 137*b516456eSPetri Karhula int ret; 138*b516456eSPetri Karhula 139*b516456eSPetri Karhula bl_data = devm_kzalloc(&pdev->dev, sizeof(*bl_data), GFP_KERNEL); 140*b516456eSPetri Karhula if (!bl_data) 141*b516456eSPetri Karhula return -ENOMEM; 142*b516456eSPetri Karhula 143*b516456eSPetri Karhula bl_data->dev = &pdev->dev; 144*b516456eSPetri Karhula bl_data->cgbc = cgbc; 145*b516456eSPetri Karhula 146*b516456eSPetri Karhula ret = cgbc_bl_read_brightness(bl_data); 147*b516456eSPetri Karhula if (ret < 0) 148*b516456eSPetri Karhula return dev_err_probe(&pdev->dev, ret, 149*b516456eSPetri Karhula "Failed to read initial brightness\n"); 150*b516456eSPetri Karhula 151*b516456eSPetri Karhula props.type = BACKLIGHT_PLATFORM; 152*b516456eSPetri Karhula props.max_brightness = CGBC_BL_MAX_BRIGHTNESS; 153*b516456eSPetri Karhula props.brightness = bl_data->current_brightness; 154*b516456eSPetri Karhula props.scale = BACKLIGHT_SCALE_LINEAR; 155*b516456eSPetri Karhula 156*b516456eSPetri Karhula bl_dev = devm_backlight_device_register(&pdev->dev, "cgbc-backlight", 157*b516456eSPetri Karhula &pdev->dev, bl_data, 158*b516456eSPetri Karhula &cgbc_bl_ops, &props); 159*b516456eSPetri Karhula if (IS_ERR(bl_dev)) 160*b516456eSPetri Karhula return dev_err_probe(&pdev->dev, PTR_ERR(bl_dev), 161*b516456eSPetri Karhula "Failed to register backlight device\n"); 162*b516456eSPetri Karhula 163*b516456eSPetri Karhula platform_set_drvdata(pdev, bl_data); 164*b516456eSPetri Karhula 165*b516456eSPetri Karhula return 0; 166*b516456eSPetri Karhula } 167*b516456eSPetri Karhula 168*b516456eSPetri Karhula static struct platform_driver cgbc_bl_driver = { 169*b516456eSPetri Karhula .driver = { 170*b516456eSPetri Karhula .name = "cgbc-backlight", 171*b516456eSPetri Karhula }, 172*b516456eSPetri Karhula .probe = cgbc_bl_probe, 173*b516456eSPetri Karhula }; 174*b516456eSPetri Karhula 175*b516456eSPetri Karhula module_platform_driver(cgbc_bl_driver); 176*b516456eSPetri Karhula 177*b516456eSPetri Karhula MODULE_AUTHOR("Petri Karhula <petri.karhula@novatron.fi>"); 178*b516456eSPetri Karhula MODULE_DESCRIPTION("Congatec Board Controller (CGBC) Backlight Driver"); 179*b516456eSPetri Karhula MODULE_LICENSE("GPL"); 180*b516456eSPetri Karhula MODULE_ALIAS("platform:cgbc-backlight"); 181