From patchwork Wed Aug 2 16:07:43 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 709433 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DF29BC04A6A for ; Wed, 2 Aug 2023 16:08:16 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229642AbjHBQIO (ORCPT ); Wed, 2 Aug 2023 12:08:14 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53638 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235411AbjHBQH5 (ORCPT ); Wed, 2 Aug 2023 12:07:57 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id AB194134 for ; Wed, 2 Aug 2023 09:07:54 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 40FB961A36 for ; Wed, 2 Aug 2023 16:07:54 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id B20B8C433CB; Wed, 2 Aug 2023 16:07:52 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992473; bh=ni13gyMmmTO9FMgSjd9qCvK8PHGZ+Lo3T9LWOQxNuvI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=d6RSx+D5cUbwjGevipQIfZWA/2Gwer8epQa1I2KqsCHu/py50aF+h+TCMjeQz5w12 BqHFY3jbEvMlyND8XDK8qDfI7mBbf5V5yNyAnSw+8fHZ0x60c+M9pEySv+joH+lFkP 6/Q0CZydk+7Mdjs75/mj+tiPpcyFlECFHsuBfw9NfQhKoqpXwdMTX5zxSg70BVaF7x pUXJkDN70qoVenKzatSbI21wMBM5CsnSlLyvri4Z2sRx36U/DoRIpx6Jw5UbKWid70 0n8RbTw6sboE52x/fE0h0GYwzHfQw/iRl6aw3+si4AFgyW29OinXNhHDzKDEg9qCSu H5DAmXd9YNBiA== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 1/6] leds: turris-omnia: drop unnecessary mutex locking Date: Wed, 2 Aug 2023 18:07:43 +0200 Message-ID: <20230802160748.11208-2-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Do not lock driver mutex in the global LED panel brightness sysfs accessors brightness_show() and brightness_store(). The mutex locking is unnecessary here. The I2C transfers are guarded by I2C core locking mechanism, and the LED commands itself do not interfere with other commands. Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs") Signed-off-by: Marek Behún Reviewed-by: Lee Jones --- drivers/leds/leds-turris-omnia.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 64b2d7b6d3f3..bbd610100e41 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -156,12 +156,9 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf) { struct i2c_client *client = to_i2c_client(dev); - struct omnia_leds *leds = i2c_get_clientdata(client); int ret; - mutex_lock(&leds->lock); ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); - mutex_unlock(&leds->lock); if (ret < 0) return ret; @@ -173,7 +170,6 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); - struct omnia_leds *leds = i2c_get_clientdata(client); unsigned long brightness; int ret; @@ -183,15 +179,10 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, if (brightness > 100) return -EINVAL; - mutex_lock(&leds->lock); ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, (u8)brightness); - mutex_unlock(&leds->lock); - - if (ret < 0) - return ret; - return count; + return ret < 0 ? ret : count; } static DEVICE_ATTR_RW(brightness); From patchwork Wed Aug 2 16:07:44 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 709432 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D3790C04FE0 for ; Wed, 2 Aug 2023 16:08:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229913AbjHBQIR (ORCPT ); Wed, 2 Aug 2023 12:08:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53638 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235485AbjHBQH7 (ORCPT ); Wed, 2 Aug 2023 12:07:59 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4BAD01981 for ; Wed, 2 Aug 2023 09:07:56 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 9C11461A24 for ; Wed, 2 Aug 2023 16:07:55 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 18101C433CA; Wed, 2 Aug 2023 16:07:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992475; bh=1sJMn5zHlsQRF/6HJlV+cFo3X9IzKb30odH+NLo3VR0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tVJE8MFZGjWq0wK+tblZdE0Y5DhXdXrr+bNj0AfFGiHslHBJBndkoE6jO3kkX6nvY kHF8h/38ztd0G9PcWkjb8jYFx3IYyaEORVIUoAgqNQrk2mxVrAjviVuuEeIRapIdmO 86fCHYos2TWEhtQshyudG8cWmxUNVu9HJRnmLbQjc5/Nb5J+JUjVJl/9M6tWZOt+8Y KpyVQbSp7O6USa/1pekxJEWYcyruyDZLRc98CUNj/3gj2L+ekWeG6q9tQyGv2OJbQ5 Tt1L0PaYpp/zA8IrOdDC4Q6YZd/illyq4hXHX4KTRE1E4hwBmKxFd8Nisd7gw84fvK F+ttdJ6p1vjag== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 2/6] leds: turris-omnia: do not use SMBUS calls Date: Wed, 2 Aug 2023 18:07:44 +0200 Message-ID: <20230802160748.11208-3-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org The leds-turris-omnia driver uses three function for I2C access: - i2c_smbus_write_byte_data() and i2c_smbus_read_byte_data(), which cause an emulated SMBUS transfer, - i2c_master_send(), which causes an ordinary I2C transfer. The Turris Omnia MCU LED controller is not semantically SMBUS, it operates as a simple I2C bus. It does not implement any of the SMBUS specific features, like PEC, or procedure calls, or anything. Moreover the I2C controller driver also does not implement SMBUS, and so the emulated SMBUS procedure from drivers/i2c/i2c-core-smbus.c is used for the SMBUS calls, which gives an unnecessary overhead. When I first wrote the driver, I was unaware of these facts, and I simply used the first function that worked. Drop the I2C SMBUS calls and instead use simple I2C transfers. Fixes: 089381b27abe ("leds: initial support for Turris Omnia LEDs") Signed-off-by: Marek Behún --- drivers/leds/leds-turris-omnia.c | 56 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index bbd610100e41..bb2a2b411a56 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -2,7 +2,7 @@ /* * CZ.NIC's Turris Omnia LEDs driver * - * 2020 by Marek Behún + * 2020, 2023 by Marek Behún */ #include @@ -41,6 +41,40 @@ struct omnia_leds { struct omnia_led leds[]; }; +static int omnia_cmd_write(const struct i2c_client *client, u8 cmd, u8 val) +{ + u8 buf[2] = { cmd, val }; + int ret; + + ret = i2c_master_send(client, buf, sizeof(buf)); + + return ret < 0 ? ret : 0; +} + +static int omnia_cmd_read(const struct i2c_client *client, u8 cmd) +{ + struct i2c_msg msgs[2]; + u8 reply; + int ret; + + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &cmd; + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 1; + msgs[1].buf = &reply; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (likely(ret == ARRAY_SIZE(msgs))) + return reply; + else if (ret < 0) + return ret; + else + return -EIO; +} + static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, enum led_brightness brightness) { @@ -64,7 +98,7 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, if (buf[2] || buf[3] || buf[4]) state |= CMD_LED_STATE_ON; - ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state); + ret = omnia_cmd_write(leds->client, CMD_LED_STATE, state); if (ret >= 0 && (state & CMD_LED_STATE_ON)) ret = i2c_master_send(leds->client, buf, 5); @@ -114,9 +148,8 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; /* put the LED into software mode */ - ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg) | - CMD_LED_MODE_USER); + ret = omnia_cmd_write(client, CMD_LED_MODE, CMD_LED_MODE_LED(led->reg) | + CMD_LED_MODE_USER); if (ret < 0) { dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, ret); @@ -124,8 +157,8 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, } /* disable the LED */ - ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, - CMD_LED_STATE_LED(led->reg)); + ret = omnia_cmd_write(client, CMD_LED_STATE, + CMD_LED_STATE_LED(led->reg)); if (ret < 0) { dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); return ret; @@ -158,7 +191,7 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, struct i2c_client *client = to_i2c_client(dev); int ret; - ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); + ret = omnia_cmd_read(client, CMD_LED_GET_BRIGHTNESS); if (ret < 0) return ret; @@ -179,8 +212,7 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, if (brightness > 100) return -EINVAL; - ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, - (u8)brightness); + ret = omnia_cmd_write(client, CMD_LED_SET_BRIGHTNESS, brightness); return ret < 0 ? ret : count; } @@ -237,8 +269,8 @@ static void omnia_leds_remove(struct i2c_client *client) u8 buf[5]; /* put all LEDs into default (HW triggered) mode */ - i2c_smbus_write_byte_data(client, CMD_LED_MODE, - CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); + omnia_cmd_write(client, CMD_LED_MODE, + CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); /* set all LEDs color to [255, 255, 255] */ buf[0] = CMD_LED_COLOR; From patchwork Wed Aug 2 16:07:45 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 710458 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1E786C001DF for ; Wed, 2 Aug 2023 16:08:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229689AbjHBQIP (ORCPT ); Wed, 2 Aug 2023 12:08:15 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53730 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235494AbjHBQIA (ORCPT ); Wed, 2 Aug 2023 12:08:00 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [139.178.84.217]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B146B171B for ; Wed, 2 Aug 2023 09:07:57 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 15FC361A3D for ; Wed, 2 Aug 2023 16:07:57 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 72933C433CC; Wed, 2 Aug 2023 16:07:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992476; bh=avOfQmV9Z9Lnbmep5+IC25tsNLI6S6GVFISxDXZdVvc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CqJiJNrH8ipg3m6gOM0AuzAcarWZEKP4l3FLswivObKCd7GWI7to6HThaXNNbgsiu sed4V/TJFE2+kmX0X5ic2ErrvAPZSVrvLYzgEpRMrEVcP/buLccgDtJ88As2O6UyY4 lNGSBAxwTKB0rft++gO1FRKDUUY5clmBBphRpfhf0BPvv0p/rHGMLOGFtrl4GquZIS FhmJ9yURL8+egjxVIFQ0pm4OW7w/o5GxmzLFqBjWzxAzzUnVgKZKw9CWphVM0Wucoe 8e05GtbMvMM/Lp+pr3nmfq2r4heuXZGUQHdma+JWAMdPs1lL5pc8UPfQgmGUAnSQm3 UImtrb00e41VA== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 3/6] leds: turris-omnia: use sysfs_emit() instead of sprintf() Date: Wed, 2 Aug 2023 18:07:45 +0200 Message-ID: <20230802160748.11208-4-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Use the dedicated sysfs_emit() function instead of sprintf() in sysfs attribute accessor brightness_show(). Signed-off-by: Marek Behún --- drivers/leds/leds-turris-omnia.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index bb2a2b411a56..9fca0acb2270 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -196,7 +196,7 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, if (ret < 0) return ret; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", ret); } static ssize_t brightness_store(struct device *dev, struct device_attribute *a, From patchwork Wed Aug 2 16:07:46 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 710457 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6ECCFC04E69 for ; Wed, 2 Aug 2023 16:08:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229500AbjHBQIS (ORCPT ); Wed, 2 Aug 2023 12:08:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53746 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235502AbjHBQIC (ORCPT ); Wed, 2 Aug 2023 12:08:02 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DBCBC1717 for ; Wed, 2 Aug 2023 09:07:58 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 710E6619D7 for ; Wed, 2 Aug 2023 16:07:58 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id E182CC43391; Wed, 2 Aug 2023 16:07:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992477; bh=YuhQ607F0knUKWC/KhDlqPiKqIGESz8PWgB3tp8FDD4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FoVuYb77VLlgmNeX0JcwCT2+opV3mljL1MruPwzRvY+DZ+O22147smewq8HgxpYkQ 4tvEzvWTemz1Qx/4ARqozVqzoYTQeHK95uyPyRr/amYJ2iIOMrEnYX+CsGt30NvL4A tkzdOvcH1XMYDiU+dj/YMtvjbZSHcdx1gUfBj3Q3cO5pca/UtMiG2ov0pNZWxynG8w +w1BzWkroT9jGLpHnI/tlOCpVCgcteWHUCz0RlHODi4Shuyz8JEiBFHnTbQft5vrAl sVO2pzKioM7asyLiQ+QoCMMOpS8i4cpTOC+sOzxOlAHvvBvkuuim3BS/fY8i4mCgTu vHdG6g/HClKfw== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 4/6] leds: turris-omnia: make set_brightness() more efficient Date: Wed, 2 Aug 2023 18:07:46 +0200 Message-ID: <20230802160748.11208-5-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Implement caching of the LED color and state values that are sent to MCU in order to make the set_brightness() operation more efficient by avoiding I2C transactions which are not needed. On Turris Omnia's MCU, which acts as the RGB LED controller, each LED has a RGB color, and a ON/OFF state, which are configurable via I2C commands CMD_LED_COLOR and CMD_LED_STATE. The CMD_LED_COLOR command sends 5 bytes and the CMD_LED_STATE command 2 bytes over the I2C bus, which operates at 100 kHz. With I2C overhead this allows only ~1670 color changing commands and ~3200 state changing commands per second. Currently, every time LED brightness or LED multi intensity is changed, we send a CMD_LED_STATE command, and if the computed color (brightness adjusted multi_intensity) is non-zero, we also send a CMD_LED_COLOR command. Consider for example the situation when we have a netdev trigger enabled for a LED. The netdev trigger does not change the LED color, only the brightness (either to 0 or to currently configured brightness), and so there is no need to send the CMD_LED_COLOR command. But each change of brightness to 0 sends one CMD_LED_STATE command, and each change of brightness to max_brightness sends one CMD_LED_STATE command and one CMD_LED_COLOR command: set_brightness(0) -> CMD_LED_STATE set_brightness(255) -> CMD_LED_STATE + CMD_LED_COLOR (unnecessary) We can avoid the unnecessary I2C transactions if we cache the values of state and color that are sent to the controller. If the color does not change from the one previously sent, there is no need to do the CMD_LED_COLOR I2C transaction, and if the state does not change, there is no need to do the CMD_LED_STATE transaction. Because we need to make sure that out cached values are consistent with the controller state, add explicit setting of the LED color to white at probe time (this is the default setting when MCU resets, but does not necessarily need to be the case, for example if U-Boot played with the LED colors). Signed-off-by: Marek Behún --- drivers/leds/leds-turris-omnia.c | 96 ++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 9fca0acb2270..636c6f802bcf 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -30,6 +30,8 @@ struct omnia_led { struct led_classdev_mc mc_cdev; struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; + u8 cached_channels[OMNIA_LED_NUM_CHANNELS]; + bool on; int reg; }; @@ -75,36 +77,82 @@ static int omnia_cmd_read(const struct i2c_client *client, u8 cmd) return -EIO; } +static int omnia_led_send_color_cmd(const struct i2c_client *client, + struct omnia_led *led) +{ + char cmd[5]; + int ret; + + cmd[0] = CMD_LED_COLOR; + cmd[1] = led->reg; + cmd[2] = led->subled_info[0].brightness; + cmd[3] = led->subled_info[1].brightness; + cmd[4] = led->subled_info[2].brightness; + + /* send the color change command */ + ret = i2c_master_send(client, cmd, 5); + if (ret < 0) + return ret; + + /* cache the RGB channel brightnesses */ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) + led->cached_channels[i] = led->subled_info[i].brightness; + + return 0; +} + +/* determine if the computed RGB channels are different from the cached ones */ +static bool omnia_led_channels_changed(struct omnia_led *led) +{ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) + if (led->subled_info[i].brightness != led->cached_channels[i]) + return true; + + return false; +} + static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); struct omnia_led *led = to_omnia_led(mc_cdev); - u8 buf[5], state; - int ret; + int err = 0; mutex_lock(&leds->lock); - led_mc_calc_color_components(&led->mc_cdev, brightness); + /* + * Only recalculate RGB brightnesses from intensities if brightness is + * non-zero. Otherwise we won't be using them and we can save ourselves + * some software divisions (Omnia's CPU does not implement the division + * instruction). + */ + if (brightness) { + led_mc_calc_color_components(mc_cdev, brightness); + + /* + * Send color command only if brightness is non-zero and the RGB + * channel brightnesses changed. + */ + if (omnia_led_channels_changed(led)) + err = omnia_led_send_color_cmd(leds->client, led); + } - buf[0] = CMD_LED_COLOR; - buf[1] = led->reg; - buf[2] = mc_cdev->subled_info[0].brightness; - buf[3] = mc_cdev->subled_info[1].brightness; - buf[4] = mc_cdev->subled_info[2].brightness; + /* send on/off state change only if (bool)brightness changed */ + if (!err && !brightness != !led->on) { + u8 state = CMD_LED_STATE_LED(led->reg); - state = CMD_LED_STATE_LED(led->reg); - if (buf[2] || buf[3] || buf[4]) - state |= CMD_LED_STATE_ON; + if (brightness) + state |= CMD_LED_STATE_ON; - ret = omnia_cmd_write(leds->client, CMD_LED_STATE, state); - if (ret >= 0 && (state & CMD_LED_STATE_ON)) - ret = i2c_master_send(leds->client, buf, 5); + err = omnia_cmd_write(leds->client, CMD_LED_STATE, state); + if (!err) + led->on = !!brightness; + } mutex_unlock(&leds->lock); - return ret; + return err; } static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, @@ -132,11 +180,15 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, } led->subled_info[0].color_index = LED_COLOR_ID_RED; - led->subled_info[0].channel = 0; led->subled_info[1].color_index = LED_COLOR_ID_GREEN; - led->subled_info[1].channel = 1; led->subled_info[2].color_index = LED_COLOR_ID_BLUE; - led->subled_info[2].channel = 2; + + /* initial color is white */ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) { + led->subled_info[i].intensity = 255; + led->subled_info[i].brightness = 255; + led->subled_info[i].channel = i; + } led->mc_cdev.subled_info = led->subled_info; led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; @@ -164,6 +216,14 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, return ret; } + /* set initial color and cache it */ + ret = omnia_led_send_color_cmd(client, led); + if (ret < 0) { + dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np, + ret); + return ret; + } + ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); if (ret < 0) { From patchwork Wed Aug 2 16:07:47 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 709431 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 25BBAC04A6A for ; Wed, 2 Aug 2023 16:08:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229893AbjHBQIT (ORCPT ); Wed, 2 Aug 2023 12:08:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53710 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235505AbjHBQID (ORCPT ); Wed, 2 Aug 2023 12:08:03 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 09A1DE1 for ; Wed, 2 Aug 2023 09:08:00 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id CF56861A11 for ; Wed, 2 Aug 2023 16:07:59 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 49988C433CB; Wed, 2 Aug 2023 16:07:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992479; bh=Eh4a4Cmi0fMxCLThk0tuikiWmumK9rJeYwlVVJTxXN0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fV+SgY4XZypo6Bd8KKZJn352uz/wy5W/kgwnCMhjPYs3rFiyE30NlOXKYpd/XpeBF nhVF5krP2P7L2fYmoOSCcuelraoDUDlwUNaa3J4k10L8euudGH0iN99o8DtucGNfz4 4n0cl8AIMI7Rz5uNMBxxK6hXRF6bgOjabgOCoHfOavj9UzWjFkfBJD+59e5QImxqu1 Yd4MSCGEt3VuZ/ws/jN3QOJ8vDkDk3hxEDeFjkntgAglZBqRSPVztdtkxxH9SiSrTW 3b1KRabrKc/Vdaxe1tanT2UTs36L7u2eBOJ9+soKgZxvqZxIbbecL3Z7ZUb74JSZPJ wrbEYk+m/lYSw== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 5/6] leds: turris-omnia: support HW controlled mode via private trigger Date: Wed, 2 Aug 2023 18:07:47 +0200 Message-ID: <20230802160748.11208-6-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Add support for enabling MCU controlled mode of the Turris Omnia LEDs via a LED private trigger called "omnia-mcu". Recall that private LED triggers will only be listed in the sysfs trigger file for LEDs that support them (currently there is no user of this mechanism). When in MCU controlled mode, the user can still set LED color, but the blinking is done by MCU, which does different things for different LEDs: - WAN LED is blinked according to the LED[0] pin of the WAN PHY - LAN LEDs are blinked according to the LED[0] output of the corresponding port of the LAN switch - PCIe LEDs are blinked according to the logical OR of the MiniPCIe port LED pins In the future I want to make the netdev trigger to transparently offload the blinking to the HW if user sets compatible settings for the netdev trigger (for LEDs associated with network devices). There was some work on this already, and hopefully we will be able to complete it sometime, but for now there are still multiple blockers for this, and even if there weren't, we still would not be able to configure HW controlled mode for the LEDs associated with MiniPCIe ports. In the meantime let's support HW controlled mode via the private LED trigger mechanism. If, in the future, we manage to complete the netdev trigger offloading, we can still keep this private trigger for backwards compatibility, if needed. We also set "omnia-mcu" to cdev->default_trigger, so that the MCU keeps control until the user first wants to take over it. If a different default trigger is specified in device-tree via the 'linux,default-trigger' property, LED class will overwrite cdev->default_trigger, and so the DT property will be respected. Signed-off-by: Marek Behún --- drivers/leds/Kconfig | 1 + drivers/leds/leds-turris-omnia.c | 97 +++++++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6046dfeca16f..ebb3b84d7a4f 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -187,6 +187,7 @@ config LEDS_TURRIS_OMNIA depends on I2C depends on MACH_ARMADA_38X || COMPILE_TEST depends on OF + select LEDS_TRIGGERS help This option enables basic support for the LEDs found on the front side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 636c6f802bcf..180b0cbeb92e 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -31,7 +31,7 @@ struct omnia_led { struct led_classdev_mc mc_cdev; struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; u8 cached_channels[OMNIA_LED_NUM_CHANNELS]; - bool on; + bool on, hwtrig; int reg; }; @@ -123,12 +123,14 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, /* * Only recalculate RGB brightnesses from intensities if brightness is - * non-zero. Otherwise we won't be using them and we can save ourselves - * some software divisions (Omnia's CPU does not implement the division - * instruction). + * non-zero (if it is zero and the LED is in HW blinking mode, we use + * max_brightness as brightness). Otherwise we won't be using them and + * we can save ourselves some software divisions (Omnia's CPU does not + * implement the division instruction). */ - if (brightness) { - led_mc_calc_color_components(mc_cdev, brightness); + if (brightness || led->hwtrig) { + led_mc_calc_color_components(mc_cdev, brightness ?: + cdev->max_brightness); /* * Send color command only if brightness is non-zero and the RGB @@ -138,8 +140,11 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, err = omnia_led_send_color_cmd(leds->client, led); } - /* send on/off state change only if (bool)brightness changed */ - if (!err && !brightness != !led->on) { + /* + * Send on/off state change only if (bool)brightness changed and the LED + * is not being blinked by HW. + */ + if (!err && !led->hwtrig && !brightness != !led->on) { u8 state = CMD_LED_STATE_LED(led->reg); if (brightness) @@ -155,6 +160,70 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, return err; } +static struct led_hw_trigger_type omnia_hw_trigger_type; + +static int omnia_hwtrig_activate(struct led_classdev *cdev) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); + struct omnia_led *led = to_omnia_led(mc_cdev); + int err = 0; + + mutex_lock(&leds->lock); + + if (!led->on) { + /* + * If the LED is off (brightness was set to 0), the last + * configured color was not necessarily sent to the MCU. + * Recompute with max_brightness and send if needed. + */ + led_mc_calc_color_components(mc_cdev, cdev->max_brightness); + + if (omnia_led_channels_changed(led)) + err = omnia_led_send_color_cmd(leds->client, led); + } + + if (!err) { + /* put the LED into MCU controlled mode */ + err = omnia_cmd_write(leds->client, CMD_LED_MODE, + CMD_LED_MODE_LED(led->reg)); + if (!err) + led->hwtrig = true; + } + + mutex_unlock(&leds->lock); + + return err; +} + +static void omnia_hwtrig_deactivate(struct led_classdev *cdev) +{ + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); + struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev)); + int err; + + mutex_lock(&leds->lock); + + led->hwtrig = false; + + /* put the LED into software mode */ + err = omnia_cmd_write(leds->client, CMD_LED_MODE, + CMD_LED_MODE_LED(led->reg) | CMD_LED_MODE_USER); + + mutex_unlock(&leds->lock); + + if (err < 0) + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n", + err); +} + +static struct led_trigger omnia_hw_trigger = { + .name = "omnia-mcu", + .activate = omnia_hwtrig_activate, + .deactivate = omnia_hwtrig_deactivate, + .trigger_type = &omnia_hw_trigger_type, +}; + static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, struct device_node *np) { @@ -198,6 +267,12 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, cdev = &led->mc_cdev.led_cdev; cdev->max_brightness = 255; cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; + cdev->trigger_type = &omnia_hw_trigger_type; + /* + * Use the omnia-mcu trigger as the default trigger. It may be rewritten + * by LED class from the linux,default-trigger property. + */ + cdev->default_trigger = omnia_hw_trigger.name; /* put the LED into software mode */ ret = omnia_cmd_write(client, CMD_LED_MODE, CMD_LED_MODE_LED(led->reg) | @@ -310,6 +385,12 @@ static int omnia_leds_probe(struct i2c_client *client) mutex_init(&leds->lock); + ret = devm_led_trigger_register(dev, &omnia_hw_trigger); + if (ret < 0) { + dev_err(dev, "Cannot register private LED trigger: %d\n", ret); + return ret; + } + led = &leds->leds[0]; for_each_available_child_of_node(np, child) { ret = omnia_led_register(client, led, child); From patchwork Wed Aug 2 16:07:48 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 710456 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 452F2C001DF for ; Wed, 2 Aug 2023 16:08:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229715AbjHBQIU (ORCPT ); Wed, 2 Aug 2023 12:08:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53708 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235506AbjHBQID (ORCPT ); Wed, 2 Aug 2023 12:08:03 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [139.178.84.217]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5906212B for ; Wed, 2 Aug 2023 09:08:01 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 2D79761A18 for ; Wed, 2 Aug 2023 16:08:01 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id A3AD2C433CA; Wed, 2 Aug 2023 16:07:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1690992480; bh=Yo0fYUXUgUMnZcJCMfq67yt1itDpOQApYHMfzIk+3V4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=aeZ6V6ssQvRxUyAUMOOhBo/3IyP0kUWlANBo1DOH5vxkckvEWkbfY/iLmqHBYNuZJ wW4EZZin4Zz3DYxViiV044O6oQbRSy0L/yStCGqGRmxcTi8TLjoIZ55R1tYw0864b3 833qV8sPyVFbHINUFElEVhySgLsTsaAnZRqgyOu843g4WGUu2EBb2cCREv1Zhc5i3+ VW12xl8Sw68QoOZYfVpw6meCwTirLvXLgfieREO3RTJOAHLjPpWmvlMN4mxDWyDqD6 GqD0o5a61LONq8ip1dJmThkEaUlvpJjCcPU1wTLuWqoc6xp4MUv5EQbgT8YBI6dR9v T/tUN0tJ6FXRw== From: =?utf-8?q?Marek_Beh=C3=BAn?= To: Pavel Machek , Lee Jones , linux-leds@vger.kernel.org Cc: =?utf-8?q?Marek_Beh=C3=BAn?= Subject: [PATCH v3 6/6] leds: turris-omnia: add support for enabling/disabling HW gamma correction Date: Wed, 2 Aug 2023 18:07:48 +0200 Message-ID: <20230802160748.11208-7-kabel@kernel.org> X-Mailer: git-send-email 2.41.0 In-Reply-To: <20230802160748.11208-1-kabel@kernel.org> References: <20230802160748.11208-1-kabel@kernel.org> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org If the MCU on Turris Omnia is running newer firmware versions, the LED controller supports RGB gamma correction (and enables it by default for newer boards). Determine whether the gamma correction setting feature is supported and add the ability to set it via sysfs attribute file. Signed-off-by: Marek Behún --- .../sysfs-class-led-driver-turris-omnia | 14 ++ drivers/leds/leds-turris-omnia.c | 135 +++++++++++++++--- 2 files changed, 132 insertions(+), 17 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia b/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia index c4d46970c1cf..369b4ae8be5f 100644 --- a/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia +++ b/Documentation/ABI/testing/sysfs-class-led-driver-turris-omnia @@ -12,3 +12,17 @@ Description: (RW) On the front panel of the Turris Omnia router there is also able to change this setting from software. Format: %i + +What: /sys/class/leds//device/gamma_correction +Date: August 2023 +KernelVersion: 6.6 +Contact: Marek Behún +Description: (RW) Newer versions of the microcontroller firmware of the + Turris Omnia router support gamma correction for the RGB LEDs. + This feature can be enabled/disabled by writing to this file. + + If the feature is not supported because the MCU firmware is too + old, the file always reads as 0, and writing to the file results + in the EOPNOTSUPP error. + + Format: %i diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 180b0cbeb92e..75cc7d2cf6d1 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -15,17 +15,30 @@ #define OMNIA_BOARD_LEDS 12 #define OMNIA_LED_NUM_CHANNELS 3 -#define CMD_LED_MODE 3 -#define CMD_LED_MODE_LED(l) ((l) & 0x0f) -#define CMD_LED_MODE_USER 0x10 +/* MCU controller commands at I2C address 0x2a */ +#define OMNIA_MCU_I2C_ADDR 0x2a -#define CMD_LED_STATE 4 -#define CMD_LED_STATE_LED(l) ((l) & 0x0f) -#define CMD_LED_STATE_ON 0x10 +#define CMD_GET_STATUS_WORD 0x01 +#define STS_FEATURES_SUPPORTED BIT(2) -#define CMD_LED_COLOR 5 -#define CMD_LED_SET_BRIGHTNESS 7 -#define CMD_LED_GET_BRIGHTNESS 8 +#define CMD_GET_FEATURES 0x10 +#define FEAT_LED_GAMMA_CORRECTION BIT(5) + +/* LED controller commands at I2C address 0x2b */ +#define CMD_LED_MODE 0x03 +#define CMD_LED_MODE_LED(l) ((l) & 0x0f) +#define CMD_LED_MODE_USER 0x10 + +#define CMD_LED_STATE 0x04 +#define CMD_LED_STATE_LED(l) ((l) & 0x0f) +#define CMD_LED_STATE_ON 0x10 + +#define CMD_LED_COLOR 0x05 +#define CMD_LED_SET_BRIGHTNESS 0x07 +#define CMD_LED_GET_BRIGHTNESS 0x08 + +#define CMD_SET_GAMMA_CORRECTION 0x30 +#define CMD_GET_GAMMA_CORRECTION 0x31 struct omnia_led { struct led_classdev_mc mc_cdev; @@ -40,6 +53,7 @@ struct omnia_led { struct omnia_leds { struct i2c_client *client; struct mutex lock; + bool has_gamma_correction; struct omnia_led leds[]; }; @@ -53,30 +67,42 @@ static int omnia_cmd_write(const struct i2c_client *client, u8 cmd, u8 val) return ret < 0 ? ret : 0; } -static int omnia_cmd_read(const struct i2c_client *client, u8 cmd) +static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, + void *reply, size_t len) { struct i2c_msg msgs[2]; - u8 reply; int ret; - msgs[0].addr = client->addr; + msgs[0].addr = addr; msgs[0].flags = 0; msgs[0].len = 1; msgs[0].buf = &cmd; - msgs[1].addr = client->addr; + msgs[1].addr = addr; msgs[1].flags = I2C_M_RD; - msgs[1].len = 1; - msgs[1].buf = &reply; + msgs[1].len = len; + msgs[1].buf = reply; - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); if (likely(ret == ARRAY_SIZE(msgs))) - return reply; + return 0; else if (ret < 0) return ret; else return -EIO; } +static int omnia_cmd_read(const struct i2c_client *client, u8 cmd) +{ + u8 reply; + int ret; + + ret = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); + if (ret < 0) + return ret; + + return reply; +} + static int omnia_led_send_color_cmd(const struct i2c_client *client, struct omnia_led *led) { @@ -353,12 +379,74 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, } static DEVICE_ATTR_RW(brightness); +static ssize_t gamma_correction_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_leds *leds = i2c_get_clientdata(client); + int ret; + + if (leds->has_gamma_correction) { + ret = omnia_cmd_read(client, CMD_GET_GAMMA_CORRECTION); + if (ret < 0) + return ret; + } else { + ret = 0; + } + + return sysfs_emit(buf, "%d\n", !!ret); +} + +static ssize_t gamma_correction_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_leds *leds = i2c_get_clientdata(client); + bool val; + int ret; + + if (!leds->has_gamma_correction) + return -EOPNOTSUPP; + + if (kstrtobool(buf, &val) < 0) + return -EINVAL; + + ret = omnia_cmd_write(client, CMD_SET_GAMMA_CORRECTION, val); + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR_RW(gamma_correction); + static struct attribute *omnia_led_controller_attrs[] = { &dev_attr_brightness.attr, + &dev_attr_gamma_correction.attr, NULL, }; ATTRIBUTE_GROUPS(omnia_led_controller); +static int omnia_mcu_get_features(const struct i2c_client *client) +{ + u16 reply; + int err; + + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, + CMD_GET_STATUS_WORD, &reply, sizeof(reply)); + if (err < 0) + return err; + + /* check whether MCU firmware supports the CMD_GET_FEAUTRES command */ + if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) + return 0; + + err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, + CMD_GET_FEATURES, &reply, sizeof(reply)); + if (err < 0) + return err; + + return le16_to_cpu(reply); +} + static int omnia_leds_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -383,6 +471,19 @@ static int omnia_leds_probe(struct i2c_client *client) leds->client = client; i2c_set_clientdata(client, leds); + ret = omnia_mcu_get_features(client); + if (ret < 0) { + dev_err(dev, "Cannot determine MCU supported features: %d\n", + ret); + return ret; + } + + leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; + if (!leds->has_gamma_correction) + dev_info(dev, + "Your board's MCU firmware does not support the LED gamma correction feature.\n" + " Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); + mutex_init(&leds->lock); ret = devm_led_trigger_register(dev, &omnia_hw_trigger);