diff --git a/BUILDS.md b/BUILDS.md
index d17b57901..21c27b038 100644
--- a/BUILDS.md
+++ b/BUILDS.md
@@ -121,6 +121,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_LM75AD | - | - / x | - | x | - | - |
| USE_APDS9960 | - | - / - | - | - | - | - |
| USE_MCP230xx | - | - / - | - | - | - | - |
+| USE_PCA9632 | - | - / - | - | - | - | - |
| USE_PCA9685 | - | - / - | - | - | - | - |
| USE_MPR121 | - | - / - | - | - | - | - |
| USE_CCS811 | - | - / - | - | x | - | - |
diff --git a/I2CDEVICES.md b/I2CDEVICES.md
index 744c3ccbf..9c9da3c0d 100644
--- a/I2CDEVICES.md
+++ b/I2CDEVICES.md
@@ -109,3 +109,4 @@ Index | Define | Driver | Device | Address(es) | Description
72 | USE_INA3221 | xsns_100 | INA3221 | 0x40-0x43 | 3-channels Voltage and Current sensor
73 | USE_HMC5883L | xsns_101 | HMC5883L | 0x1E | 3-channels Magnetic Field Sensor
74 | USE_DISPLAY_TM1650 | xdsp_20 | TM1650 | 0x24 - 0x27, 0x34 - 0x37 | Four-digit seven-segment LED controller
+ 75 | USE_PCA9632 | xdrv_91 | PCA9632 | 0x60 | 4-channel 4-bit pwm driver
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 4fe411b69..e95f42e14 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -632,6 +632,12 @@
// #define USE_PCA9685 // [I2cDriver1] Enable PCA9685 I2C HW PWM Driver - Must define I2C Address in #define USE_PCA9685_ADDR below - range 0x40 - 0x47 (+1k4 code)
// #define USE_PCA9685_ADDR 0x40 // Enable PCA9685 I2C Address to use (Must be within range 0x40 through 0x47 - set according to your wired setup)
// #define USE_PCA9685_FREQ 50 // Define default PWM frequency in Hz to be used (must be within 24 to 1526) - If other value is used, it will rever to 50Hz
+// #define USE_PCA9632 // [I2cDriver75] Enable PCA9632 I2C HW PWM Driver
+// #define USE_PCA9632_ADDR 0x60 // Define PCA9685 I2C Address to use (Must be within range 0x60 through 0x63 - set according to your wired setup)
+// #define USE_PCA9632_CM_0 0 // Mapping for channel 0
+// #define USE_PCA9632_CM_1 1 // Mapping for channel 1
+// #define USE_PCA9632_CM_2 2 // Mapping for channel 2
+// #define USE_PCA9632_CM_3 3 // Mapping for channel 3
// #define USE_MPR121 // [I2cDriver23] Enable MPR121 controller (I2C addresses 0x5A, 0x5B, 0x5C and 0x5D) in input mode for touch buttons (+1k3 code)
// #define USE_CCS811 // [I2cDriver24] Enable CCS811 sensor (I2C address 0x5A) (+2k2 code)
// #define USE_CCS811_V2 // [I2cDriver24] Enable CCS811 sensor (I2C addresses 0x5A and 0x5B) (+2k8 code)
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_91_pca9632.ino b/tasmota/tasmota_xdrv_driver/xdrv_91_pca9632.ino
new file mode 100644
index 000000000..acf7d1c60
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_91_pca9632.ino
@@ -0,0 +1,275 @@
+/*
+ xdrv_91_pca9632.ino - Support for I2C PCA9632 4-channel 8-bit hardware PWM driver on Tasmota
+
+ Copyright (C) 2022 Pascal Heinrich
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_I2C
+#ifdef USE_PCA9632
+/*********************************************************************************************\
+ * PCA9632 - 4-channel 8-bit pwm driver
+ *
+ * I2C Address: 0x60 .. 0x63
+\*********************************************************************************************/
+
+#define XDRV_91 91
+#define XI2C_75 75 // See I2CDEVICES.md
+
+#define PCA9632_REG_MODE1 0x00
+#define PCA9632_REG_MODE2 0x01
+#define PCA9632_REG_PWM_BASE 0x02
+#define PCA9632_REG_PWM_1 PCA9632_REG_PWM_BASE + 0
+#define PCA9632_REG_PWM_2 PCA9632_REG_PWM_BASE + 1
+#define PCA9632_REG_PWM_3 PCA9632_REG_PWM_BASE + 2
+#define PCA9632_REG_PWM_4 PCA9632_REG_PWM_BASE + 3
+#define PCA9632_REG_GRPPWM 0x06
+#define PCA9632_REG_GRPGREQ 0x07
+#define PCA9632_REG_LEDOUT 0x08
+#define PCA9632_AUTO_INC 0x80
+
+#ifndef USE_PCA9632_ADDR
+ #define USE_PCA9632_ADDR 0x62
+#endif
+
+#ifndef USE_PCA9632_CM_0
+ #define USE_PCA9632_CM_0 0
+#endif
+
+#ifndef USE_PCA9632_CM_1
+ #define USE_PCA9632_CM_1 1
+#endif
+
+#ifndef USE_PCA9632_CM_2
+ #define USE_PCA9632_CM_2 2
+#endif
+
+#ifndef USE_PCA9632_CM_3
+ #define USE_PCA9632_CM_3 3
+#endif
+
+bool pca9632_inverted = false; // invert PWM for open-collector load
+bool pca9632_detected = false;
+uint8_t pca9632_pin_pwm_value[4];
+
+bool PCA9632_Detect(void) {
+ if (I2cSetDevice(USE_PCA9632_ADDR)) {
+ uint8_t buffer;
+ if (I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE1)) {
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x10);
+ if (I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE1)) {
+ if (0x10 == buffer) {
+ I2cSetActiveFound(USE_PCA9632_ADDR, "PCA9632");
+ PCA9632_Reset(); // Reset the controller
+ return pca9632_detected = true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void PCA9632_Init(void) {
+ // configure none inverted and totem pole
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE2, 0x14);
+
+ // turn off sleep mode
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x1);
+}
+
+void PCA9632_Reset(void) {
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE1, 0x6);
+ pca9632_inverted = false;
+ for (uint8_t pin = 0; pin < 4; pin++) {
+ PCA9632_SetPWM(pin, 0);
+ pca9632_pin_pwm_value[pin] = 0;
+ }
+ Response_P(PSTR("{\"PCA9632\":{\"RESET\":\"OK\"}}"));
+}
+
+bool PCA9632_SetInvert(bool on) {
+ uint8_t buffer;
+ if(I2cValidRead8(&buffer, USE_PCA9632_ADDR, PCA9632_REG_MODE2)) {
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_MODE2, buffer | ((on ? 1 : 0) >> 4));
+ }
+ return on;
+}
+
+bool PCA9632_SetPWM(uint8_t pin, uint8_t pwm) {
+
+ uint8_t pin_mapping = PCA9632_PinMapping(pin);
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_PWM_BASE + pin_mapping, pwm);
+ pca9632_pin_pwm_value[pin_mapping] = pwm;
+
+ return pwm > 0;
+
+}
+
+bool PCA9632_SetPWM_Bulk(uint8_t *pwm, uint16_t len) {
+ uint8_t buffer[4];
+
+ // map the pwm values to the final pins
+ for (uint8_t pin = 0; pin < 4; pin++) {
+ uint8_t pin_mapping = PCA9632_PinMapping(pin);
+ buffer[pin_mapping] = pwm[pin];
+ }
+
+ I2cWriteBuffer(USE_PCA9632_ADDR, PCA9632_REG_PWM_BASE | PCA9632_AUTO_INC, buffer, len);
+
+ // set the pwm values for later use
+ bool enable = false;
+ for (uint8_t pin = 0; pin < 4; pin++) {
+ uint8_t value = buffer[pin];
+
+ pca9632_pin_pwm_value[pin] = value;
+
+ if (value > 0) {
+ enable |= true;
+ }
+ }
+
+ return enable;
+}
+
+void PCA9632_Enable(bool enable) {
+ DEBUG_TRACE_LOG(PSTR("DRV: PCA9632 enable %d"), enable);
+ I2cWrite8(USE_PCA9632_ADDR, PCA9632_REG_LEDOUT, enable ? 0xFF : 0x0);
+}
+
+bool PCA9632_Command(void) {
+ bool serviced = true;
+ bool validpin = false;
+ uint8_t paramcount = 0;
+ if (XdrvMailbox.data_len > 0) {
+ paramcount = 1;
+ } else {
+ serviced = false;
+ return serviced;
+ }
+ char argument[XdrvMailbox.data_len];
+ for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) {
+ if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
+ if (',' == XdrvMailbox.data[ca]) { paramcount++; }
+ }
+ UpperCase(XdrvMailbox.data, XdrvMailbox.data);
+
+ if (!strcmp(ArgV(argument, 1),"RESET")) { PCA9632_Reset(); return serviced; }
+
+ if (!strcmp(ArgV(argument, 1),"STATUS")) { PCA9632_OutputTelemetry(false); return serviced; }
+
+ if (!strcmp(ArgV(argument, 1),"INVERT")) {
+ if (paramcount > 1) {
+ pca9632_inverted = PCA9632_SetInvert(1 == atoi(ArgV(argument, 2)));
+ Response_P(PSTR("{\"PCA9632\":{\"INVERT\":%i, \"Result\":\"OK\"}}"), pca9632_inverted);
+ return serviced;
+ } else { // No parameter was given for invert, so we return current setting
+ Response_P(PSTR("{\"PCA9632\":{\"INVERT\":%i}}"), pca9632_inverted);
+ return serviced;
+ }
+ }
+ if (!strcmp(ArgV(argument, 1),"PWM")) {
+ if (paramcount > 1) {
+ uint8_t pin = atoi(ArgV(argument, 2));
+ if (paramcount > 2) {
+ if (!strcmp(ArgV(argument, 3), "ON")) {
+ PCA9632_SetPWM(pin, 255);
+ Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, 255);
+ serviced = true;
+ return serviced;
+ }
+ if (!strcmp(ArgV(argument, 3), "OFF")) {
+ PCA9632_SetPWM(pin, 0);
+ Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, 0);
+ serviced = true;
+ return serviced;
+ }
+ uint16_t pwm = atoi(ArgV(argument, 3));
+ if ((pin >= 0 && pin <= 3) && (pwm >= 0 && pwm <= 255)) {
+ PCA9632_SetPWM(pin, pwm);
+ Response_P(PSTR("{\"PCA9632\":{\"PIN\":%i,\"PWM\":%i}}"), pin, pwm);
+ serviced = true;
+ return serviced;
+ }
+ }
+ }
+ }
+ if (!strcmp(ArgV(argument, 1),"ENABLE")) {
+ PCA9632_Enable(true);
+ Response_P(PSTR("{\"PCA9632\":{\"ENABLE\":true}}"));
+ }
+ if (!strcmp(ArgV(argument, 1),"DISABLE")) {
+ PCA9632_Enable(false);
+ Response_P(PSTR("{\"PCA9632\":{\"ENABLE\":false}}"));
+ }
+ return serviced;
+}
+
+void PCA9632_OutputTelemetry(bool telemetry) {
+ ResponseAppend_P(PSTR("\"INVERT\":%i,"), pca9632_inverted?1:0);
+ for (uint32_t pin = 0; pin < 4; pin++) {
+ uint8_t pin_mapping = PCA9632_PinMapping(pin);
+ ResponseAppend_P(PSTR("\"PWM%i\":%i,"), pin_mapping, pca9632_pin_pwm_value[pin_mapping]);
+ }
+ ResponseAppend_P(PSTR("\"END\":1}}"));
+ if (telemetry) {
+ MqttPublishTeleSensor();
+ }
+}
+
+uint8_t PCA9632_PinMapping(uint8_t pin) {
+ switch(pin) {
+ case 0:
+ return USE_PCA9632_CM_0;
+ case 1:
+ return USE_PCA9632_CM_1;
+ case 2:
+ return USE_PCA9632_CM_2;
+ case 3:
+ return USE_PCA9632_CM_3;
+ default:
+ return USE_PCA9632_CM_0;
+ }
+}
+
+bool Xdrv91(uint32_t function) {
+ if (!I2cEnabled(XI2C_75)) { return false; }
+
+ bool result = false;
+
+ if (FUNC_INIT == function) {
+ if (PCA9632_Detect()) {
+ PCA9632_Init();
+ }
+ }
+ else if (pca9632_detected) {
+ switch (function) {
+ case FUNC_EVERY_SECOND:
+ if (TasmotaGlobal.tele_period == 0) {
+ PCA9632_OutputTelemetry(true);
+ }
+ break;
+ case FUNC_COMMAND_DRIVER:
+ if (XDRV_91 == XdrvMailbox.index) {
+ result = PCA9632_Command();
+ }
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_PCA9632
+#endif // USE_IC2
diff --git a/tasmota/tasmota_xlgt_light/xlgt_11_pca9632.ino b/tasmota/tasmota_xlgt_light/xlgt_11_pca9632.ino
new file mode 100644
index 000000000..5d408a607
--- /dev/null
+++ b/tasmota/tasmota_xlgt_light/xlgt_11_pca9632.ino
@@ -0,0 +1,79 @@
+/*
+ xlgt_11_pca9632.ino - pca9632 four channel led support for Tasmota
+
+ Copyright (C) 2021 Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_LIGHT
+#ifdef USE_PCA9632
+/*********************************************************************************************\
+ * PCA9632 - Controlling RGBW over I2C
+ * Copyright (C) 2022 Pascal Heinrich
+ *
+\*********************************************************************************************/
+
+#define XLGT_11 11
+
+/********************************************************************************************/
+
+bool PCA9632_SetChannels(void) {
+ uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
+
+ DEBUG_TRACE_LOG(PSTR("LGT: PCA9632 %d - %d - %d - %d"), cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
+
+ PCA9632_Enable(PCA9632_SetPWM_Bulk(cur_col, 4));
+
+ return true;
+}
+
+bool PCA9632_ModuleSelected(void) {
+ DEBUG_TRACE_LOG(PSTR("LGT: PCA9632 ModuleSelected"));
+
+ if (PCA9632_Detect()) {
+ PCA9632_Init();
+
+ TasmotaGlobal.light_type += LST_RGBW; // Add RGBW to be controlled by PCA9632
+ TasmotaGlobal.light_driver = XLGT_11;
+
+ AddLog(LOG_LEVEL_INFO, PSTR("LGT: PCA9632 Found"));
+
+ return true;
+ }
+
+ return false;
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xlgt11(uint32_t function) {
+ bool result = false;
+
+ switch (function) {
+ case FUNC_SET_CHANNELS:
+ result = PCA9632_SetChannels();
+ break;
+ case FUNC_MODULE_INIT:
+ result = PCA9632_ModuleSelected();
+ break;
+ }
+ return result;
+}
+
+#endif // USE_PCA9632
+#endif // USE_LIGHT
+