From 56240912f92185264a4f3e509d962f3bf87d6de1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 10 Oct 2021 20:56:57 +0200 Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device The clk and regulator frameworks expect clk/regulator consumer-devices to have info about the consumed clks/regulators described in the device's fw_node. To work around cases where this info is not present in the firmware tables, which is often the case on x86/ACPI devices, both frameworks allow the provider-driver to attach info about consumers to the clks/regulators when registering these. This causes problems with the probe ordering wrt drivers for consumers of these clks/regulators. Since the lookups are only registered when the provider-driver binds, trying to get these clks/regulators before then results in a -ENOENT error for clks and a dummy regulator for regulators. One case where we hit this issue is camera sensors such as e.g. the OV8865 sensor found on the Microsoft Surface Go. The sensor uses clks, regulators and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 ACPI device. There is special platform code handling this and setting platform_data with the necessary consumer info on the MFD cells instantiated for the PMIC under: drivers/platform/x86/intel/int3472. For this to work properly the ov8865 driver must not bind to the I2C-client for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and clk MFD cells have all been fully setup. The OV8865 on the Microsoft Surface Go is just one example, all X86 devices using the Intel IPU3 camera block found on recent Intel SoCs have similar issues where there is an INT3472 HID ACPI-device, which describes the clks and regulators, and the driver for this INT3472 device must be fully initialized before the sensor driver (any sensor driver) binds for things to work properly. On these devices the ACPI nodes describing the sensors all have a _DEP dependency on the matching INT3472 ACPI device (there is one per sensor). This allows solving the probe-ordering problem by delaying the enumeration (instantiation of the I2C-client in the ov8865 example) of ACPI-devices which have a _DEP dependency on an INT3472 device. The new acpi_dev_ready_for_enumeration() helper used for this is also exported because for devices, which have the enumeration_by_parent flag set, the parent-driver will do its own scan of child ACPI devices and it will try to enumerate those during its probe(). Code doing this such as e.g. the i2c-core-acpi.c code must call this new helper to ensure that it too delays the enumeration until all the _DEP dependencies are met on devices which have the new honor_deps flag set. Signed-off-by: Hans de Goede Patchset: cameras --- drivers/acpi/scan.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 0c6f06abe3f47..4fc320f424e8e 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2106,6 +2106,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, static void acpi_default_enumeration(struct acpi_device *device) { + if (!acpi_dev_ready_for_enumeration(device)) + return; + /* * Do not enumerate devices with enumeration_by_parent flag set as * they will be enumerated by their respective parents. -- 2.40.0 From 9f596394ab56d947ca5de48c2f8128b05025fe46 Mon Sep 17 00:00:00 2001 From: zouxiaoh Date: Fri, 25 Jun 2021 08:52:59 +0800 Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, The IPU driver allocates its own page table that is not mapped via the DMA, and thus the Intel IOMMU driver blocks access giving this error: DMAR: DRHD: handling fault status reg 3 DMAR: [DMA Read] Request device [00:05.0] PASID ffffffff fault addr 76406000 [fault reason 06] PTE Read access is not set As IPU is not an external facing device which is not risky, so use IOMMU passthrough mode for Intel IPUs. Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 Tracked-On: #JIITL8-411 Signed-off-by: Bingbu Cao Signed-off-by: zouxiaoh Signed-off-by: Xu Chongyang Patchset: cameras --- drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c index 08e35f9e67a62..a8f20384dfd4b 100644 --- a/drivers/iommu/intel/iommu.c +++ b/drivers/iommu/intel/iommu.c @@ -37,6 +37,12 @@ #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9a19 || \ + (pdev)->device == 0x9a39 || \ + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ((pdev)->device == 0x9d3e)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) @@ -290,12 +296,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_gfx = 1; static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; static int iommu_skip_te_disable; #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 #define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; @@ -2589,6 +2597,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) return IOMMU_DOMAIN_IDENTITY; } @@ -2980,6 +2991,9 @@ static int __init init_dmars(void) if (!dmar_map_gfx) iommu_identity_mapping |= IDENTMAP_GFX; + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + if (!dmar_map_ipts) iommu_identity_mapping |= IDENTMAP_IPTS; @@ -4823,6 +4837,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) dmar_map_gfx = 0; } +static void quirk_iommu_ipu(struct pci_dev *dev) +{ + if (!IS_INTEL_IPU(dev)) + return; + + if (risky_device(dev)) + return; + + pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); + dmar_map_ipu = 0; +} + static void quirk_iommu_ipts(struct pci_dev *dev) { if (!IS_IPTS(dev)) @@ -4834,6 +4860,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) pci_info(dev, "Passthrough IOMMU for IPTS\n"); dmar_map_ipts = 0; } + /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); @@ -4869,6 +4896,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + /* disable IPTS dmar support */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -- 2.40.0 From 7df4522c095c27ccbe662cd1e72e8e6116173d28 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Sun, 10 Oct 2021 20:57:02 +0200 Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic can be forwarded to a device connected to the PMIC as though it were connected directly to the system bus. Enable this mode when the chip is initialised. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 5b8d1a9620a5d..6a0ff035cf209 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) return ret; } + /* Enable I2C daisy chain */ + ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); + if (ret) { + dev_err(dev, "Failed to enable i2c daisy chain\n"); + return ret; + } + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); return 0; -- 2.40.0 From b5d9851b25d2f41c2d8b566a227695eb40e6eb3b Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 28 Oct 2021 21:55:16 +0100 Subject: [PATCH] media: i2c: Add driver for DW9719 VCM Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice and registers a control to set the desired focus. Signed-off-by: Daniel Scally Patchset: cameras --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/dw9719.c | 425 +++++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 drivers/media/i2c/dw9719.c diff --git a/MAINTAINERS b/MAINTAINERS index f77188f30210f..164d6078a6a32 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6362,6 +6362,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.yaml F: drivers/media/i2c/dw9714.c +DONGWOON DW9719 LENS VOICE COIL DRIVER +M: Daniel Scally +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/dw9719.c + DONGWOON DW9768 LENS VOICE COIL DRIVER M: Dongchun Zhu L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 833241897d637..8cfd7b6c4bf54 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -847,6 +847,17 @@ config VIDEO_DW9714 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. +config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" + depends on I2C && VIDEO_V4L2 + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC + help + This is a driver for the DW9719 camera lens voice coil. + This is designed for linear control of voice coil motors, + controlled via I2C serial interface. + config VIDEO_DW9768 tristate "DW9768 lens voice coil support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 4d6c052bb5a7f..29e4d61ce310f 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o obj-$(CONFIG_VIDEO_CX25840) += cx25840/ obj-$(CONFIG_VIDEO_DW9714) += dw9714.o +obj-$(CONFIG_VIDEO_DW9719) += dw9719.o obj-$(CONFIG_VIDEO_DW9768) += dw9768.o obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c new file mode 100644 index 0000000000000..180b04d2a6b3a --- /dev/null +++ b/drivers/media/i2c/dw9719.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012 Intel Corporation + +/* + * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: + * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DW9719_MAX_FOCUS_POS 1023 +#define DW9719_CTRL_STEPS 16 +#define DW9719_CTRL_DELAY_US 1000 +#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) + +#define DW9719_INFO 0 +#define DW9719_ID 0xF1 +#define DW9719_CONTROL 2 +#define DW9719_VCM_CURRENT 3 + +#define DW9719_MODE 6 +#define DW9719_VCM_FREQ 7 + +#define DW9719_MODE_SAC3 0x40 +#define DW9719_DEFAULT_VCM_FREQ 0x60 +#define DW9719_ENABLE_RINGING 0x02 + +#define NUM_REGULATORS 2 + +#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) + +struct dw9719_device { + struct device *dev; + struct i2c_client *client; + struct regulator_bulk_data regulators[NUM_REGULATORS]; + struct v4l2_subdev sd; + + struct dw9719_v4l2_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *focus; + } ctrls; +}; + +static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) +{ + struct i2c_msg msg[2]; + u8 buf[2] = { reg }; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = &buf[1]; + *val = 0; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) + return ret; + + *val = buf[1]; + + return 0; +} + +static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) +{ + struct i2c_msg msg; + int ret; + + u8 buf[2] = { reg, val }; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) +{ + struct i2c_msg msg; + u8 buf[3] = { reg }; + int ret; + + put_unaligned_be16(val, buf + 1); + + msg.addr = client->addr; + msg.flags = 0; + msg.len = sizeof(buf); + msg.buf = buf; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return ret < 0 ? ret : 0; +} + +static int dw9719_detect(struct dw9719_device *dw9719) +{ + int ret; + u8 val; + + ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); + if (ret < 0) + return ret; + + if (val != DW9719_ID) { + dev_err(dw9719->dev, "Failed to detect correct id\n"); + ret = -ENXIO; + } + + return 0; +} + +static int dw9719_power_down(struct dw9719_device *dw9719) +{ + return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); +} + +static int dw9719_power_up(struct dw9719_device *dw9719) +{ + int ret; + + ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); + if (ret) + return ret; + + /* Jiggle SCL pin to wake up device */ + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); + + /* Need 100us to transit from SHUTDOWN to STANDBY*/ + usleep_range(100, 1000); + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, + DW9719_ENABLE_RINGING); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); + if (ret < 0) + goto fail_powerdown; + + ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, + DW9719_DEFAULT_VCM_FREQ); + if (ret < 0) + goto fail_powerdown; + + return 0; + +fail_powerdown: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) +{ + int ret; + + value = clamp(value, 0, DW9719_MAX_FOCUS_POS); + ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); + if (ret < 0) + return ret; + + return 0; +} + +static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw9719_device *dw9719 = container_of(ctrl->handler, + struct dw9719_device, + ctrls.handler); + int ret; + + /* Only apply changes to the controls if the device is powered up */ + if (!pm_runtime_get_if_in_use(dw9719->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ret = dw9719_t_focus_abs(dw9719, ctrl->val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put(dw9719->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { + .s_ctrl = dw9719_set_ctrl, +}; + +static int __maybe_unused dw9719_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int ret; + int val; + + for (val = dw9719->ctrls.focus->val; val >= 0; + val -= DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + return ret; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return dw9719_power_down(dw9719); +} + +static int __maybe_unused dw9719_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct dw9719_device *dw9719 = to_dw9719_device(sd); + int current_focus = dw9719->ctrls.focus->val; + int ret; + int val; + + ret = dw9719_power_up(dw9719); + if (ret) + return ret; + + for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; + val += DW9719_CTRL_STEPS) { + ret = dw9719_t_focus_abs(dw9719, val); + if (ret) + goto err_power_down; + + usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); + } + + return 0; + +err_power_down: + dw9719_power_down(dw9719); + return ret; +} + +static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { + .open = dw9719_open, + .close = dw9719_close, +}; + +static int dw9719_init_controls(struct dw9719_device *dw9719) +{ + const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; + int ret; + + ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); + if (ret) + return ret; + + dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, + V4L2_CID_FOCUS_ABSOLUTE, 0, + DW9719_MAX_FOCUS_POS, 1, 0); + + if (dw9719->ctrls.handler.error) { + dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); + ret = dw9719->ctrls.handler.error; + goto err_free_handler; + } + + dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; + + return ret; + +err_free_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + return ret; +} + +static const struct v4l2_subdev_ops dw9719_ops = { }; + +static int dw9719_probe(struct i2c_client *client) +{ + struct dw9719_device *dw9719; + int ret; + + dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); + if (!dw9719) + return -ENOMEM; + + dw9719->client = client; + dw9719->dev = &client->dev; + + dw9719->regulators[0].supply = "vdd"; + /* + * The DW9719 has only the 1 VDD voltage input, but some PMICs such as + * the TPS68470 PMIC have I2C passthrough capability, to disconnect the + * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) + * is off, because some sensors then short these pins to ground; + * and the DW9719 might sit behind this passthrough, this it needs to + * enable VSIO as that will also enable the I2C passthrough. + */ + dw9719->regulators[1].supply = "vsio"; + + ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, + dw9719->regulators); + if (ret) + return dev_err_probe(&client->dev, ret, "getting regulators\n"); + + v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); + dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + dw9719->sd.internal_ops = &dw9719_internal_ops; + + ret = dw9719_init_controls(dw9719); + if (ret) + return ret; + + ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); + if (ret < 0) + goto err_free_ctrl_handler; + + dw9719->sd.entity.function = MEDIA_ENT_F_LENS; + + /* + * We need the driver to work in the event that pm runtime is disable in + * the kernel, so power up and verify the chip now. In the event that + * runtime pm is disabled this will leave the chip on, so that the lens + * will work. + */ + + ret = dw9719_power_up(dw9719); + if (ret) + goto err_cleanup_media; + + ret = dw9719_detect(dw9719); + if (ret) + goto err_powerdown; + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); + pm_runtime_enable(&client->dev); + + ret = v4l2_async_register_subdev(&dw9719->sd); + if (ret < 0) + goto err_pm_runtime; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); + + return ret; + +err_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_put_noidle(&client->dev); +err_powerdown: + dw9719_power_down(dw9719); +err_cleanup_media: + media_entity_cleanup(&dw9719->sd.entity); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + + return ret; +} + +static void dw9719_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, + sd); + + pm_runtime_disable(&client->dev); + v4l2_async_unregister_subdev(sd); + v4l2_ctrl_handler_free(&dw9719->ctrls.handler); + media_entity_cleanup(&dw9719->sd.entity); +} + +static const struct i2c_device_id dw9719_id_table[] = { + { "dw9719" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, dw9719_id_table); + +static const struct dev_pm_ops dw9719_pm_ops = { + SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) +}; + +static struct i2c_driver dw9719_i2c_driver = { + .driver = { + .name = "dw9719", + .pm = &dw9719_pm_ops, + }, + .probe_new = dw9719_probe, + .remove = dw9719_remove, + .id_table = dw9719_id_table, +}; +module_i2c_driver(dw9719_i2c_driver); + +MODULE_AUTHOR("Daniel Scally "); +MODULE_DESCRIPTION("DW9719 VCM Driver"); +MODULE_LICENSE("GPL"); -- 2.40.0 From 1b9c5b1902d5dd389fced7a51649e88834ee4c45 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 4 May 2022 23:21:45 +0100 Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to .bound() Creating links and registering subdev nodes during the .complete() callback has the unfortunate effect of preventing all cameras that connect to a notifier from working if any one of their drivers fails to probe. Moving the functionality from .complete() to .bound() allows those camera sensor drivers that did probe correctly to work regardless. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 3b76a9d0383a8..38f9f4da1922e 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1383,7 +1383,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, { struct cio2_device *cio2 = to_cio2_device(notifier); struct sensor_async_subdev *s_asd = to_sensor_asd(asd); + struct device *dev = &cio2->pci_dev->dev; struct cio2_queue *q; + unsigned int pad; + int ret; if (cio2->queue[s_asd->csi2.port].sensor) return -EBUSY; @@ -1394,7 +1397,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, q->sensor = sd; q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - return 0; + for (pad = 0; pad < q->sensor->entity.num_pads; pad++) + if (q->sensor->entity.pads[pad].flags & + MEDIA_PAD_FL_SOURCE) + break; + + if (pad == q->sensor->entity.num_pads) { + dev_err(dev, "failed to find src pad for %s\n", + q->sensor->name); + return -ENXIO; + } + + ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, + CIO2_PAD_SINK, 0); + if (ret) { + dev_err(dev, "failed to create link for %s\n", + q->sensor->name); + return ret; + } + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); } /* The .unbind callback */ @@ -1408,50 +1430,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, cio2->queue[s_asd->csi2.port].sensor = NULL; } -/* .complete() is called after all subdevices have been located */ -static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) -{ - struct cio2_device *cio2 = to_cio2_device(notifier); - struct device *dev = &cio2->pci_dev->dev; - struct sensor_async_subdev *s_asd; - struct v4l2_async_subdev *asd; - struct cio2_queue *q; - unsigned int pad; - int ret; - - list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { - s_asd = to_sensor_asd(asd); - q = &cio2->queue[s_asd->csi2.port]; - - for (pad = 0; pad < q->sensor->entity.num_pads; pad++) - if (q->sensor->entity.pads[pad].flags & - MEDIA_PAD_FL_SOURCE) - break; - - if (pad == q->sensor->entity.num_pads) { - dev_err(dev, "failed to find src pad for %s\n", - q->sensor->name); - return -ENXIO; - } - - ret = media_create_pad_link( - &q->sensor->entity, pad, - &q->subdev.entity, CIO2_PAD_SINK, - 0); - if (ret) { - dev_err(dev, "failed to create link for %s\n", - q->sensor->name); - return ret; - } - } - - return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -} - static const struct v4l2_async_notifier_operations cio2_async_ops = { .bound = cio2_notifier_bound, .unbind = cio2_notifier_unbind, - .complete = cio2_notifier_complete, }; static int cio2_parse_firmware(struct cio2_device *cio2) -- 2.40.0 From f449b95f198517633020e069475fbbc29a192895 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Jun 2022 22:15:56 +0100 Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 Removing the .complete() callback had some unintended consequences. Because the VCM driver is not directly linked to the ipu3-cio2 driver .bound() never gets called for it, which means its devnode is never created if it probes late. Because .complete() waits for any sub-notifiers to also be complete it is captured in that call. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index 38f9f4da1922e..82681df7d794f 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1430,9 +1430,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, cio2->queue[s_asd->csi2.port].sensor = NULL; } +/* .complete() is called after all subdevices have been located */ +static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct cio2_device *cio2 = to_cio2_device(notifier); + + return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); +} + static const struct v4l2_async_notifier_operations cio2_async_ops = { .bound = cio2_notifier_bound, .unbind = cio2_notifier_unbind, + .complete = cio2_notifier_complete, }; static int cio2_parse_firmware(struct cio2_device *cio2) -- 2.40.0 From 0ad6c3c8e0184919c51c4adb566969b9fe1e8288 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 15 Jul 2022 23:48:00 +0200 Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies It should depend on VIDEO_DEV instead of VIDEO_V4L2. Signed-off-by: Maximilian Luz Patchset: cameras --- drivers/media/i2c/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 8cfd7b6c4bf54..11b8acd7cc5fe 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -849,7 +849,7 @@ config VIDEO_DW9714 config VIDEO_DW9719 tristate "DW9719 lens voice coil support" - depends on I2C && VIDEO_V4L2 + depends on I2C && VIDEO_DEV select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select V4L2_ASYNC -- 2.40.0 From 72a302f12c71d49515f4a2fbf554160f6470c990 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 2 Mar 2023 12:59:39 +0000 Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The driver for this sensor expects a single pin named "enable", but on some Microsoft Surface platforms the sensor is assigned a single GPIO who's type flag is INT3472_GPIO_TYPE_RESET. Remap the GPIO pin's function from "reset" to "enable". This is done outside of the existing remap table since it is a more widespread discrepancy than that method is designed for. Additionally swap the polarity of the pin to match the driver's expectation. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/platform/x86/intel/int3472/discrete.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index c42c3faa2c32d..6f4b8e24eb56c 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -108,6 +108,9 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 { const struct int3472_sensor_config *sensor_config; char *path = agpio->resource_source.string_ptr; + const struct acpi_device_id ov7251_ids[] = { + { "INT347E" }, + }; struct gpiod_lookup *table_entry; struct acpi_device *adev; acpi_handle handle; @@ -130,6 +133,17 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 } } + /* + * In addition to the function remap table we need to bulk remap the + * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that + * expects its only GPIO pin to be called "enable" (and to have the + * opposite polarity). + */ + if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { + func = "enable"; + polarity = GPIO_ACTIVE_HIGH; + } + /* Functions mapped to NULL should not be mapped to the sensor */ if (!func) return 0; -- 2.40.0 From e906323a5b30c2b942e2af09765e549ed0e4c528 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 20 Jan 2023 12:45:15 +0100 Subject: [PATCH] leds: led-class: Add led_module_get() helper Split out part of of_led_get() into a generic led_module_get() helper function. This is a preparation patch for adding a generic (non devicetree specific) led_get() function. Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/leds/led-class.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index aa39b2a48fdff..743d97b082dcb 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -215,6 +215,23 @@ static int led_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); +static struct led_classdev *led_module_get(struct device *led_dev) +{ + struct led_classdev *led_cdev; + + if (!led_dev) + return ERR_PTR(-EPROBE_DEFER); + + led_cdev = dev_get_drvdata(led_dev); + + if (!try_module_get(led_cdev->dev->parent->driver->owner)) { + put_device(led_cdev->dev); + return ERR_PTR(-ENODEV); + } + + return led_cdev; +} + /** * of_led_get() - request a LED device via the LED framework * @np: device node to get the LED device from @@ -226,7 +243,6 @@ static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume); struct led_classdev *of_led_get(struct device_node *np, int index) { struct device *led_dev; - struct led_classdev *led_cdev; struct device_node *led_node; led_node = of_parse_phandle(np, "leds", index); @@ -235,19 +251,8 @@ struct led_classdev *of_led_get(struct device_node *np, int index) led_dev = class_find_device_by_of_node(leds_class, led_node); of_node_put(led_node); - put_device(led_dev); - - if (!led_dev) - return ERR_PTR(-EPROBE_DEFER); - - led_cdev = dev_get_drvdata(led_dev); - if (!try_module_get(led_cdev->dev->parent->driver->owner)) { - put_device(led_cdev->dev); - return ERR_PTR(-ENODEV); - } - - return led_cdev; + return led_module_get(led_dev); } EXPORT_SYMBOL_GPL(of_led_get); -- 2.40.0 From e90a4c3af7b393ee587006a73738c47b0937e537 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 20 Jan 2023 12:45:16 +0100 Subject: [PATCH] leds: led-class: Add __devm_led_get() helper Add a __devm_led_get() helper which registers a passed in led_classdev with devm for unregistration. This is a preparation patch for adding a generic (non devicetree specific) devm_led_get() function. Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/leds/led-class.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 743d97b082dcb..4904d140a560a 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -274,6 +274,22 @@ static void devm_led_release(struct device *dev, void *res) led_put(*p); } +static struct led_classdev *__devm_led_get(struct device *dev, struct led_classdev *led) +{ + struct led_classdev **dr; + + dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), GFP_KERNEL); + if (!dr) { + led_put(led); + return ERR_PTR(-ENOMEM); + } + + *dr = led; + devres_add(dev, dr); + + return led; +} + /** * devm_of_led_get - Resource-managed request of a LED device * @dev: LED consumer @@ -289,7 +305,6 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, int index) { struct led_classdev *led; - struct led_classdev **dr; if (!dev) return ERR_PTR(-EINVAL); @@ -298,17 +313,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, if (IS_ERR(led)) return led; - dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), - GFP_KERNEL); - if (!dr) { - led_put(led); - return ERR_PTR(-ENOMEM); - } - - *dr = led; - devres_add(dev, dr); - - return led; + return __devm_led_get(dev, led); } EXPORT_SYMBOL_GPL(devm_of_led_get); -- 2.40.0 From 2fd15407f1aebf2344211d9789644d0cc8cfe7c1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 20 Jan 2023 12:45:17 +0100 Subject: [PATCH] leds: led-class: Add generic [devm_]led_get() Add a generic [devm_]led_get() method which can be used on both devicetree and non devicetree platforms to get a LED classdev associated with a specific function on a specific device, e.g. the privacy LED associated with a specific camera sensor. Note unlike of_led_get() this takes a string describing the function rather then an index. This is done because e.g. camera sensors might have a privacy LED, or a flash LED, or both and using an index approach leaves it unclear what the function of index 0 is if there is only 1 LED. This uses a lookup-table mechanism for non devicetree platforms. This allows the platform code to map specific LED class_dev-s to a specific device,function combinations this way. For devicetree platforms getting the LED by function-name could be made to work using the standard devicetree pattern of adding a -names string array to map names to the indexes. Reviewed-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/leds/led-class.c | 84 ++++++++++++++++++++++++++++++++++++++++ include/linux/leds.h | 21 ++++++++++ 2 files changed, 105 insertions(+) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 4904d140a560a..0c4b8d8d2b4fe 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -23,6 +23,8 @@ #include "leds.h" static struct class *leds_class; +static DEFINE_MUTEX(leds_lookup_lock); +static LIST_HEAD(leds_lookup_list); static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -317,6 +319,88 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, } EXPORT_SYMBOL_GPL(devm_of_led_get); +/** + * led_get() - request a LED device via the LED framework + * @dev: device for which to get the LED device + * @con_id: name of the LED from the device's point of view + * + * @return a pointer to a LED device or ERR_PTR(errno) on failure. + */ +struct led_classdev *led_get(struct device *dev, char *con_id) +{ + struct led_lookup_data *lookup; + const char *provider = NULL; + struct device *led_dev; + + mutex_lock(&leds_lookup_lock); + list_for_each_entry(lookup, &leds_lookup_list, list) { + if (!strcmp(lookup->dev_id, dev_name(dev)) && + !strcmp(lookup->con_id, con_id)) { + provider = kstrdup_const(lookup->provider, GFP_KERNEL); + break; + } + } + mutex_unlock(&leds_lookup_lock); + + if (!provider) + return ERR_PTR(-ENOENT); + + led_dev = class_find_device_by_name(leds_class, provider); + kfree_const(provider); + + return led_module_get(led_dev); +} +EXPORT_SYMBOL_GPL(led_get); + +/** + * devm_led_get() - request a LED device via the LED framework + * @dev: device for which to get the LED device + * @con_id: name of the LED from the device's point of view + * + * The LED device returned from this function is automatically released + * on driver detach. + * + * @return a pointer to a LED device or ERR_PTR(errno) on failure. + */ +struct led_classdev *devm_led_get(struct device *dev, char *con_id) +{ + struct led_classdev *led; + + led = led_get(dev, con_id); + if (IS_ERR(led)) + return led; + + return __devm_led_get(dev, led); +} +EXPORT_SYMBOL_GPL(devm_led_get); + +/** + * led_add_lookup() - Add a LED lookup table entry + * @led_lookup: the lookup table entry to add + * + * Add a LED lookup table entry. On systems without devicetree the lookup table + * is used by led_get() to find LEDs. + */ +void led_add_lookup(struct led_lookup_data *led_lookup) +{ + mutex_lock(&leds_lookup_lock); + list_add_tail(&led_lookup->list, &leds_lookup_list); + mutex_unlock(&leds_lookup_lock); +} +EXPORT_SYMBOL_GPL(led_add_lookup); + +/** + * led_remove_lookup() - Remove a LED lookup table entry + * @led_lookup: the lookup table entry to remove + */ +void led_remove_lookup(struct led_lookup_data *led_lookup) +{ + mutex_lock(&leds_lookup_lock); + list_del(&led_lookup->list); + mutex_unlock(&leds_lookup_lock); +} +EXPORT_SYMBOL_GPL(led_remove_lookup); + static int led_classdev_next_name(const char *init_name, char *name, size_t len) { diff --git a/include/linux/leds.h b/include/linux/leds.h index ba4861ec73d30..31cb74b90ffcd 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -39,6 +39,21 @@ enum led_default_state { LEDS_DEFSTATE_KEEP = 2, }; +/** + * struct led_lookup_data - represents a single LED lookup entry + * + * @list: internal list of all LED lookup entries + * @provider: name of led_classdev providing the LED + * @dev_id: name of the device associated with this LED + * @con_id: name of the LED from the device's point of view + */ +struct led_lookup_data { + struct list_head list; + const char *provider; + const char *dev_id; + const char *con_id; +}; + struct led_init_data { /* device fwnode handle */ struct fwnode_handle *fwnode; @@ -211,6 +226,12 @@ void devm_led_classdev_unregister(struct device *parent, void led_classdev_suspend(struct led_classdev *led_cdev); void led_classdev_resume(struct led_classdev *led_cdev); +void led_add_lookup(struct led_lookup_data *led_lookup); +void led_remove_lookup(struct led_lookup_data *led_lookup); + +struct led_classdev *__must_check led_get(struct device *dev, char *con_id); +struct led_classdev *__must_check devm_led_get(struct device *dev, char *con_id); + extern struct led_classdev *of_led_get(struct device_node *np, int index); extern void led_put(struct led_classdev *led_cdev); struct led_classdev *__must_check devm_of_led_get(struct device *dev, -- 2.40.0 From fd05482ce75c24299ab7625e658a31a406ee8406 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 20 Jan 2023 12:45:18 +0100 Subject: [PATCH] leds: led-class: Add devicetree support to led_get() Turn of_led_get() into a more generic __of_led_get() helper function, which can lookup LEDs in devicetree by either name or index. And use this new helper to add devicetree support to the generic (non devicetree specific) [devm_]led_get() function. This uses the standard devicetree pattern of adding a -names string array to map names to the indexes for an array of resources. Note the new led-names property for LED consumers is not added to the devicetree documentation because there seems to be no documentation for the leds property itself to extend it with this. It seems that how LED consumers should be described is not documented at all ATM. This patch is marked as RFC because of both the missing devicetree documentation and because there are no devicetree users of the generic [devm_]led_get() function for now. Reviewed-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/leds/led-class.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 0c4b8d8d2b4fe..2f3af6e302082 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -234,19 +234,18 @@ static struct led_classdev *led_module_get(struct device *led_dev) return led_cdev; } -/** - * of_led_get() - request a LED device via the LED framework - * @np: device node to get the LED device from - * @index: the index of the LED - * - * Returns the LED device parsed from the phandle specified in the "leds" - * property of a device tree node or a negative error-code on failure. - */ -struct led_classdev *of_led_get(struct device_node *np, int index) +static struct led_classdev *__of_led_get(struct device_node *np, int index, + const char *name) { struct device *led_dev; struct device_node *led_node; + /* + * For named LEDs, first look up the name in the "led-names" property. + * If it cannot be found, then of_parse_phandle() will propagate the error. + */ + if (name) + index = of_property_match_string(np, "led-names", name); led_node = of_parse_phandle(np, "leds", index); if (!led_node) return ERR_PTR(-ENOENT); @@ -256,6 +255,19 @@ struct led_classdev *of_led_get(struct device_node *np, int index) return led_module_get(led_dev); } + +/** + * of_led_get() - request a LED device via the LED framework + * @np: device node to get the LED device from + * @index: the index of the LED + * + * Returns the LED device parsed from the phandle specified in the "leds" + * property of a device tree node or a negative error-code on failure. + */ +struct led_classdev *of_led_get(struct device_node *np, int index) +{ + return __of_led_get(np, index, NULL); +} EXPORT_SYMBOL_GPL(of_led_get); /** @@ -329,9 +341,16 @@ EXPORT_SYMBOL_GPL(devm_of_led_get); struct led_classdev *led_get(struct device *dev, char *con_id) { struct led_lookup_data *lookup; + struct led_classdev *led_cdev; const char *provider = NULL; struct device *led_dev; + if (dev->of_node) { + led_cdev = __of_led_get(dev->of_node, -1, con_id); + if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT) + return led_cdev; + } + mutex_lock(&leds_lookup_lock); list_for_each_entry(lookup, &leds_lookup_list, list) { if (!strcmp(lookup->dev_id, dev_name(dev)) && -- 2.40.0 From c721c947fadaf0849f4cd32f2206c43c0f8c8c8e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 20 Jan 2023 12:45:19 +0100 Subject: [PATCH] media: v4l2-core: Built async and fwnode code into videodev.ko Currently the videodev.ko code may be builtin while e.g. v4l2-fwnode.ko is build as a module. This makes it hard to add code depending on other subsystems spanning both videodev.ko and v4l2-fwnode.ko. Specifically this block adding code depending on the LED subsystem. This is made even harder because CONFIG_V4L2_FWNODE is selected, not depended on so it itself cannot depend on another subsystem without editing all the Kconfig symbols selecting it to also list the dependency and there are many of such symbols. Adding a "select LED_CLASS if NEW_LEDS" to CONFIG_V4L2_FWNODE leads to Kconfig erroring out with "error: recursive dependency detected!". To fix this dependency mess, change the V4L2_FWNODE and V4L2_ASYNC (which V4L2_FWNODE selects) Kconfig symbols from tristate to bools and link their code into videodev.ko instead of making them separate modules. This will allow using IS_REACHABLE(LED_CLASS) for the new LED integration code without needing to worry that it expands to 0 in some places and 1 in other places because some of the code being builtin vs modular. On x86_64 this leads to the following size changes for videodev.ko [hans@shalem linux]$ size drivers/media/v4l2-core/videodev.ko Before: text data bss dec hex filename 218206 14395 2448 235049 39629 drivers/media/v4l2-core/videodev.ko After: text data bss dec hex filename 243213 17615 2456 263284 40474 drivers/media/v4l2-core/videodev.ko So (as expected) there is some increase in size here, but it really is not that much. And the uncompressed no-debuginfo .ko file disk-usage actually shrinks by 17 KiB (comparing the slightly larger videodev.ko against the 3 original modules) and loading time will also be better. Acked-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/media/v4l2-core/Kconfig | 4 ++-- drivers/media/v4l2-core/Makefile | 4 ++-- drivers/media/v4l2-core/v4l2-async.c | 17 ++++------------- drivers/media/v4l2-core/v4l2-dev-priv.h | 19 +++++++++++++++++++ drivers/media/v4l2-core/v4l2-dev.c | 8 ++++++++ drivers/media/v4l2-core/v4l2-fwnode.c | 6 ------ 6 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 drivers/media/v4l2-core/v4l2-dev-priv.h diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig index 348559bc24689..73574d9460105 100644 --- a/drivers/media/v4l2-core/Kconfig +++ b/drivers/media/v4l2-core/Kconfig @@ -68,11 +68,11 @@ config V4L2_FLASH_LED_CLASS When in doubt, say N. config V4L2_FWNODE - tristate + bool select V4L2_ASYNC config V4L2_ASYNC - tristate + bool # Used by drivers that need Videobuf modules config VIDEOBUF_GEN diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile index 41d91bd10cf28..8c5a1ab8d939a 100644 --- a/drivers/media/v4l2-core/Makefile +++ b/drivers/media/v4l2-core/Makefile @@ -15,7 +15,9 @@ videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ # Please keep it alphabetically sorted by Kconfig name # (e. g. LC_ALL=C sort Makefile) +videodev-$(CONFIG_V4L2_ASYNC) += v4l2-async.o videodev-$(CONFIG_COMPAT) += v4l2-compat-ioctl32.o +videodev-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o videodev-$(CONFIG_MEDIA_CONTROLLER) += v4l2-mc.o videodev-$(CONFIG_SPI) += v4l2-spi.o videodev-$(CONFIG_TRACEPOINTS) += v4l2-trace.o @@ -24,9 +26,7 @@ videodev-$(CONFIG_VIDEO_V4L2_I2C) += v4l2-i2c.o # Please keep it alphabetically sorted by Kconfig name # (e. g. LC_ALL=C sort Makefile) -obj-$(CONFIG_V4L2_ASYNC) += v4l2-async.o obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o -obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o obj-$(CONFIG_V4L2_H264) += v4l2-h264.o obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 2f1b718a91893..024d6b82b50af 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -24,6 +23,8 @@ #include #include +#include "v4l2-dev-priv.h" + static int v4l2_async_nf_call_bound(struct v4l2_async_notifier *n, struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) @@ -900,25 +901,15 @@ DEFINE_SHOW_ATTRIBUTE(pending_subdevs); static struct dentry *v4l2_async_debugfs_dir; -static int __init v4l2_async_init(void) +void __init v4l2_async_debugfs_init(void) { v4l2_async_debugfs_dir = debugfs_create_dir("v4l2-async", NULL); debugfs_create_file("pending_async_subdevices", 0444, v4l2_async_debugfs_dir, NULL, &pending_subdevs_fops); - - return 0; } -static void __exit v4l2_async_exit(void) +void __exit v4l2_async_debugfs_exit(void) { debugfs_remove_recursive(v4l2_async_debugfs_dir); } - -subsys_initcall(v4l2_async_init); -module_exit(v4l2_async_exit); - -MODULE_AUTHOR("Guennadi Liakhovetski "); -MODULE_AUTHOR("Sakari Ailus "); -MODULE_AUTHOR("Ezequiel Garcia "); -MODULE_LICENSE("GPL"); diff --git a/drivers/media/v4l2-core/v4l2-dev-priv.h b/drivers/media/v4l2-core/v4l2-dev-priv.h new file mode 100644 index 0000000000000..b5b1ee78be20a --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-dev-priv.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Video capture interface for Linux version 2 private header. + * + * Copyright (C) 2023 Hans de Goede + */ + +#ifndef _V4L2_DEV_PRIV_H_ +#define _V4L2_DEV_PRIV_H_ + +#if IS_ENABLED(CONFIG_V4L2_ASYNC) +void v4l2_async_debugfs_init(void); +void v4l2_async_debugfs_exit(void); +#else +static inline void v4l2_async_debugfs_init(void) {} +static inline void v4l2_async_debugfs_exit(void) {} +#endif + +#endif diff --git a/drivers/media/v4l2-core/v4l2-dev.c b/drivers/media/v4l2-core/v4l2-dev.c index 397d553177fa7..10ba2e4196a65 100644 --- a/drivers/media/v4l2-core/v4l2-dev.c +++ b/drivers/media/v4l2-core/v4l2-dev.c @@ -31,6 +31,8 @@ #include #include +#include "v4l2-dev-priv.h" + #define VIDEO_NUM_DEVICES 256 #define VIDEO_NAME "video4linux" @@ -1190,6 +1192,7 @@ static int __init videodev_init(void) return -EIO; } + v4l2_async_debugfs_init(); return 0; } @@ -1197,6 +1200,7 @@ static void __exit videodev_exit(void) { dev_t dev = MKDEV(VIDEO_MAJOR, 0); + v4l2_async_debugfs_exit(); class_unregister(&video_class); unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); } @@ -1205,6 +1209,10 @@ subsys_initcall(videodev_init); module_exit(videodev_exit) MODULE_AUTHOR("Alan Cox, Mauro Carvalho Chehab , Bill Dirks, Justin Schoeman, Gerd Knorr"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_AUTHOR("Sakari Ailus "); +MODULE_AUTHOR("Ezequiel Garcia "); +MODULE_AUTHOR("Sylwester Nawrocki "); MODULE_DESCRIPTION("Video4Linux2 core driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS_CHARDEV_MAJOR(VIDEO_MAJOR); diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 3d9533c1b2029..c8a2264262bca 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -1328,8 +1327,3 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) return ret; } EXPORT_SYMBOL_GPL(v4l2_async_register_subdev_sensor); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sakari Ailus "); -MODULE_AUTHOR("Sylwester Nawrocki "); -MODULE_AUTHOR("Guennadi Liakhovetski "); -- 2.40.0 From de5866d009ef5ac839e85a53f93ce04b2a92409d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 27 Jan 2023 21:37:25 +0100 Subject: [PATCH] media: v4l2-core: Make the v4l2-core code enable/disable the privacy LED if present Make v4l2_async_register_subdev_sensor() try to get a privacy LED associated with the sensor and extend the call_s_stream() wrapper to enable/disable the privacy LED if found. This makes the core handle privacy LED control, rather then having to duplicate this code in all the sensor drivers. Suggested-by: Sakari Ailus Acked-by: Linus Walleij Signed-off-by: Hans de Goede Patchset: cameras --- drivers/media/v4l2-core/v4l2-async.c | 3 ++ drivers/media/v4l2-core/v4l2-fwnode.c | 7 ++++ drivers/media/v4l2-core/v4l2-subdev-priv.h | 14 +++++++ drivers/media/v4l2-core/v4l2-subdev.c | 44 ++++++++++++++++++++++ include/media/v4l2-subdev.h | 3 ++ 5 files changed, 71 insertions(+) create mode 100644 drivers/media/v4l2-core/v4l2-subdev-priv.h diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 024d6b82b50af..b26dcb8d423e1 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -24,6 +24,7 @@ #include #include "v4l2-dev-priv.h" +#include "v4l2-subdev-priv.h" static int v4l2_async_nf_call_bound(struct v4l2_async_notifier *n, struct v4l2_subdev *subdev, @@ -823,6 +824,8 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd) if (!sd->async_list.next) return; + v4l2_subdev_put_privacy_led(sd); + mutex_lock(&list_lock); __v4l2_async_nf_unregister(sd->subdev_notifier); diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index c8a2264262bca..8501b6931d3a0 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -27,6 +27,8 @@ #include #include +#include "v4l2-subdev-priv.h" + static const struct v4l2_fwnode_bus_conv { enum v4l2_fwnode_bus_type fwnode_bus_type; enum v4l2_mbus_type mbus_type; @@ -1301,6 +1303,10 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) v4l2_async_nf_init(notifier); + ret = v4l2_subdev_get_privacy_led(sd); + if (ret < 0) + goto out_cleanup; + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); if (ret < 0) goto out_cleanup; @@ -1321,6 +1327,7 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) v4l2_async_nf_unregister(notifier); out_cleanup: + v4l2_subdev_put_privacy_led(sd); v4l2_async_nf_cleanup(notifier); kfree(notifier); diff --git a/drivers/media/v4l2-core/v4l2-subdev-priv.h b/drivers/media/v4l2-core/v4l2-subdev-priv.h new file mode 100644 index 0000000000000..7ad2812c3d8e3 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-subdev-priv.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * V4L2 sub-device pivate header. + * + * Copyright (C) 2023 Hans de Goede + */ + +#ifndef _V4L2_SUBDEV_PRIV_H_ +#define _V4L2_SUBDEV_PRIV_H_ + +int v4l2_subdev_get_privacy_led(struct v4l2_subdev *sd); +void v4l2_subdev_put_privacy_led(struct v4l2_subdev *sd); + +#endif diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 4988a25bd8f46..9fd1836282859 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -23,6 +24,8 @@ #include #include +#include "v4l2-subdev-priv.h" + #if defined(CONFIG_VIDEO_V4L2_SUBDEV_API) static int subdev_fh_init(struct v4l2_subdev_fh *fh, struct v4l2_subdev *sd) { @@ -322,6 +325,14 @@ static int call_s_stream(struct v4l2_subdev *sd, int enable) { int ret; +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + if (!IS_ERR_OR_NULL(sd->privacy_led)) { + if (enable) + led_set_brightness(sd->privacy_led, sd->privacy_led->max_brightness); + else + led_set_brightness(sd->privacy_led, 0); + } +#endif ret = sd->ops->video->s_stream(sd, enable); if (!enable && ret < 0) { @@ -1090,6 +1101,7 @@ void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops) sd->grp_id = 0; sd->dev_priv = NULL; sd->host_priv = NULL; + sd->privacy_led = NULL; #if defined(CONFIG_MEDIA_CONTROLLER) sd->entity.name = sd->name; sd->entity.obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV; @@ -1105,3 +1117,35 @@ void v4l2_subdev_notify_event(struct v4l2_subdev *sd, v4l2_subdev_notify(sd, V4L2_DEVICE_NOTIFY_EVENT, (void *)ev); } EXPORT_SYMBOL_GPL(v4l2_subdev_notify_event); + +int v4l2_subdev_get_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + sd->privacy_led = led_get(sd->dev, "privacy-led"); + if (IS_ERR(sd->privacy_led) && PTR_ERR(sd->privacy_led) != -ENOENT) + return dev_err_probe(sd->dev, PTR_ERR(sd->privacy_led), "getting privacy LED\n"); + + if (!IS_ERR_OR_NULL(sd->privacy_led)) { + mutex_lock(&sd->privacy_led->led_access); + led_sysfs_disable(sd->privacy_led); + led_trigger_remove(sd->privacy_led); + led_set_brightness(sd->privacy_led, 0); + mutex_unlock(&sd->privacy_led->led_access); + } +#endif + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_subdev_get_privacy_led); + +void v4l2_subdev_put_privacy_led(struct v4l2_subdev *sd) +{ +#if IS_REACHABLE(CONFIG_LEDS_CLASS) + if (!IS_ERR_OR_NULL(sd->privacy_led)) { + mutex_lock(&sd->privacy_led->led_access); + led_sysfs_enable(sd->privacy_led); + mutex_unlock(&sd->privacy_led->led_access); + led_put(sd->privacy_led); + } +#endif +} +EXPORT_SYMBOL_GPL(v4l2_subdev_put_privacy_led); diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index b15fa9930f30c..0547313f98cc4 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -38,6 +38,7 @@ struct v4l2_subdev; struct v4l2_subdev_fh; struct tuner_setup; struct v4l2_mbus_frame_desc; +struct led_classdev; /** * struct v4l2_decode_vbi_line - used to decode_vbi_line @@ -982,6 +983,8 @@ struct v4l2_subdev { * appropriate functions. */ + struct led_classdev *privacy_led; + /* * TODO: active_state should most likely be changed from a pointer to an * embedded field. For the time being it's kept as a pointer to more -- 2.40.0 From 7149a5e25231f6286ee3745defecf743a3ed6fb1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 27 Jan 2023 21:37:26 +0100 Subject: [PATCH] platform/x86: int3472/discrete: Refactor GPIO to sensor mapping Add a helper function to map the type returned by the _DSM method to a function name + the default polarity for that function. And fold the INT3472_GPIO_TYPE_RESET and INT3472_GPIO_TYPE_POWERDOWN cases into a single generic case. This is a preparation patch for further GPIO mapping changes. Signed-off-by: Hans de Goede Patchset: cameras --- drivers/platform/x86/intel/int3472/discrete.c | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 6f4b8e24eb56c..443f8dcb1e733 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -202,6 +202,36 @@ static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, return 0; } +static void int3472_get_func_and_polarity(u8 type, const char **func, u32 *polarity) +{ + switch (type) { + case INT3472_GPIO_TYPE_RESET: + *func = "reset"; + *polarity = GPIO_ACTIVE_LOW; + break; + case INT3472_GPIO_TYPE_POWERDOWN: + *func = "powerdown"; + *polarity = GPIO_ACTIVE_LOW; + break; + case INT3472_GPIO_TYPE_CLK_ENABLE: + *func = "clk-enable"; + *polarity = GPIO_ACTIVE_HIGH; + break; + case INT3472_GPIO_TYPE_PRIVACY_LED: + *func = "privacy-led"; + *polarity = GPIO_ACTIVE_HIGH; + break; + case INT3472_GPIO_TYPE_POWER_ENABLE: + *func = "power-enable"; + *polarity = GPIO_ACTIVE_HIGH; + break; + default: + *func = "unknown"; + *polarity = GPIO_ACTIVE_HIGH; + break; + } +} + /** * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor * @ares: A pointer to a &struct acpi_resource @@ -241,6 +271,8 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, struct acpi_resource_gpio *agpio; union acpi_object *obj; const char *err_msg; + const char *func; + u32 polarity; int ret; u8 type; @@ -264,19 +296,14 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, type = obj->integer.value & 0xff; + int3472_get_func_and_polarity(type, &func, &polarity); + switch (type) { case INT3472_GPIO_TYPE_RESET: - ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", - GPIO_ACTIVE_LOW); - if (ret) - err_msg = "Failed to map reset pin to sensor\n"; - - break; case INT3472_GPIO_TYPE_POWERDOWN: - ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", - GPIO_ACTIVE_LOW); + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, func, polarity); if (ret) - err_msg = "Failed to map powerdown pin to sensor\n"; + err_msg = "Failed to map GPIO pin to sensor\n"; break; case INT3472_GPIO_TYPE_CLK_ENABLE: -- 2.40.0 From 70ff97b13a7d99c3b64b297b44b538dc9b579336 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 27 Jan 2023 21:37:27 +0100 Subject: [PATCH] platform/x86: int3472/discrete: Create a LED class device for the privacy LED On some systems, e.g. the Lenovo ThinkPad X1 Yoga gen 7 and the ThinkPad X1 Nano gen 2 there is no clock-enable pin, triggering the: "No clk GPIO. The privacy LED won't work" warning and causing the privacy LED to not work. Fix this by modeling the privacy LED as a LED class device rather then integrating it with the registered clock. Note this relies on media subsys changes to actually turn the LED on/off when the sensor's v4l2_subdev's s_stream() operand gets called. Signed-off-by: Hans de Goede Patchset: cameras --- drivers/platform/x86/intel/int3472/Makefile | 2 +- .../x86/intel/int3472/clk_and_regulator.c | 3 - drivers/platform/x86/intel/int3472/common.h | 15 +++- drivers/platform/x86/intel/int3472/discrete.c | 58 ++++----------- drivers/platform/x86/intel/int3472/led.c | 74 +++++++++++++++++++ 5 files changed, 105 insertions(+), 47 deletions(-) create mode 100644 drivers/platform/x86/intel/int3472/led.c diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile index cfec7784c5c93..9f16cb5143973 100644 --- a/drivers/platform/x86/intel/int3472/Makefile +++ b/drivers/platform/x86/intel/int3472/Makefile @@ -1,4 +1,4 @@ obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ intel_skl_int3472_tps68470.o -intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o +intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o led.o common.o intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o common.o diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index 74dc2cff799ee..e3b597d933880 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -23,8 +23,6 @@ static int skl_int3472_clk_prepare(struct clk_hw *hw) struct int3472_gpio_clock *clk = to_int3472_clk(hw); gpiod_set_value_cansleep(clk->ena_gpio, 1); - gpiod_set_value_cansleep(clk->led_gpio, 1); - return 0; } @@ -33,7 +31,6 @@ static void skl_int3472_clk_unprepare(struct clk_hw *hw) struct int3472_gpio_clock *clk = to_int3472_clk(hw); gpiod_set_value_cansleep(clk->ena_gpio, 0); - gpiod_set_value_cansleep(clk->led_gpio, 0); } static int skl_int3472_clk_enable(struct clk_hw *hw) diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h index 53270d19c73ab..82dc37e08882e 100644 --- a/drivers/platform/x86/intel/int3472/common.h +++ b/drivers/platform/x86/intel/int3472/common.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,8 @@ #define GPIO_REGULATOR_NAME_LENGTH 21 #define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 +#define INT3472_LED_MAX_NAME_LEN 32 + #define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 #define INT3472_REGULATOR(_name, _supply, _ops) \ @@ -96,10 +99,16 @@ struct int3472_discrete_device { struct clk_hw clk_hw; struct clk_lookup *cl; struct gpio_desc *ena_gpio; - struct gpio_desc *led_gpio; u32 frequency; } clock; + struct int3472_pled { + struct led_classdev classdev; + struct led_lookup_data lookup; + char name[INT3472_LED_MAX_NAME_LEN]; + struct gpio_desc *gpio; + } pled; + unsigned int ngpios; /* how many GPIOs have we seen */ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ struct gpiod_lookup_table gpios; @@ -119,4 +128,8 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, struct acpi_resource_gpio *agpio); void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); +int skl_int3472_register_pled(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio, u32 polarity); +void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472); + #endif diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 443f8dcb1e733..67d26f8a8d9a3 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -169,37 +169,21 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 } static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio, u8 type) + struct acpi_resource_gpio *agpio) { char *path = agpio->resource_source.string_ptr; u16 pin = agpio->pin_table[0]; struct gpio_desc *gpio; - switch (type) { - case INT3472_GPIO_TYPE_CLK_ENABLE: - gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - - int3472->clock.ena_gpio = gpio; - /* Ensure the pin is in output mode and non-active state */ - gpiod_direction_output(int3472->clock.ena_gpio, 0); - break; - case INT3472_GPIO_TYPE_PRIVACY_LED: - gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); + gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); - int3472->clock.led_gpio = gpio; - /* Ensure the pin is in output mode and non-active state */ - gpiod_direction_output(int3472->clock.led_gpio, 0); - break; - default: - dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); - break; - } + int3472->clock.ena_gpio = gpio; + /* Ensure the pin is in output mode and non-active state */ + gpiod_direction_output(int3472->clock.ena_gpio, 0); - return 0; + return skl_int3472_register_clock(int3472); } static void int3472_get_func_and_polarity(u8 type, const char **func, u32 *polarity) @@ -307,11 +291,16 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_CLK_ENABLE: - case INT3472_GPIO_TYPE_PRIVACY_LED: - ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); + ret = skl_int3472_map_gpio_to_clk(int3472, agpio); if (ret) err_msg = "Failed to map GPIO to clock\n"; + break; + case INT3472_GPIO_TYPE_PRIVACY_LED: + ret = skl_int3472_register_pled(int3472, agpio, polarity); + if (ret) + err_msg = "Failed to register LED\n"; + break; case INT3472_GPIO_TYPE_POWER_ENABLE: ret = skl_int3472_register_regulator(int3472, agpio); @@ -355,21 +344,6 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) acpi_dev_free_resource_list(&resource_list); - /* - * If we find no clock enable GPIO pin then the privacy LED won't work. - * We've never seen that situation, but it's possible. Warn the user so - * it's clear what's happened. - */ - if (int3472->clock.ena_gpio) { - ret = skl_int3472_register_clock(int3472); - if (ret) - return ret; - } else { - if (int3472->clock.led_gpio) - dev_warn(int3472->dev, - "No clk GPIO. The privacy LED won't work\n"); - } - int3472->gpios.dev_id = int3472->sensor_name; gpiod_add_lookup_table(&int3472->gpios); @@ -386,8 +360,8 @@ static int skl_int3472_discrete_remove(struct platform_device *pdev) skl_int3472_unregister_clock(int3472); gpiod_put(int3472->clock.ena_gpio); - gpiod_put(int3472->clock.led_gpio); + skl_int3472_unregister_pled(int3472); skl_int3472_unregister_regulator(int3472); return 0; diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c new file mode 100644 index 0000000000000..251c6524458e7 --- /dev/null +++ b/drivers/platform/x86/intel/int3472/led.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Hans de Goede */ + +#include +#include +#include +#include "common.h" + +static int int3472_pled_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct int3472_discrete_device *int3472 = + container_of(led_cdev, struct int3472_discrete_device, pled.classdev); + + gpiod_set_value_cansleep(int3472->pled.gpio, brightness); + return 0; +} + +int skl_int3472_register_pled(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio, u32 polarity) +{ + char *p, *path = agpio->resource_source.string_ptr; + int ret; + + if (int3472->pled.classdev.dev) + return -EBUSY; + + int3472->pled.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], + "int3472,privacy-led"); + if (IS_ERR(int3472->pled.gpio)) + return dev_err_probe(int3472->dev, PTR_ERR(int3472->pled.gpio), + "getting privacy LED GPIO\n"); + + if (polarity == GPIO_ACTIVE_LOW) + gpiod_toggle_active_low(int3472->pled.gpio); + + /* Ensure the pin is in output mode and non-active state */ + gpiod_direction_output(int3472->pled.gpio, 0); + + /* Generate the name, replacing the ':' in the ACPI devname with '_' */ + snprintf(int3472->pled.name, sizeof(int3472->pled.name), + "%s::privacy_led", acpi_dev_name(int3472->sensor)); + p = strchr(int3472->pled.name, ':'); + *p = '_'; + + int3472->pled.classdev.name = int3472->pled.name; + int3472->pled.classdev.max_brightness = 1; + int3472->pled.classdev.brightness_set_blocking = int3472_pled_set; + + ret = led_classdev_register(int3472->dev, &int3472->pled.classdev); + if (ret) + goto err_free_gpio; + + int3472->pled.lookup.provider = int3472->pled.name; + int3472->pled.lookup.dev_id = int3472->sensor_name; + int3472->pled.lookup.con_id = "privacy-led"; + led_add_lookup(&int3472->pled.lookup); + + return 0; + +err_free_gpio: + gpiod_put(int3472->pled.gpio); + return ret; +} + +void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472) +{ + if (IS_ERR_OR_NULL(int3472->pled.classdev.dev)) + return; + + led_remove_lookup(&int3472->pled.lookup); + led_classdev_unregister(&int3472->pled.classdev); + gpiod_put(int3472->pled.gpio); +} -- 2.40.0 From 581e535b2341fbcb14838f8fc621adbe2cdedab9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 27 Jan 2023 21:37:28 +0100 Subject: [PATCH] platform/x86: int3472/discrete: Move GPIO request to skl_int3472_register_clock() Move the requesting of the clk-enable GPIO to skl_int3472_register_clock() (and move the gpiod_put to unregister). This mirrors the GPIO handling in skl_int3472_register_regulator() and allows removing skl_int3472_map_gpio_to_clk() from discrete.c. Signed-off-by: Hans de Goede Patchset: cameras --- .../x86/intel/int3472/clk_and_regulator.c | 28 +++++++++++++++-- drivers/platform/x86/intel/int3472/common.h | 3 +- drivers/platform/x86/intel/int3472/discrete.c | 30 ++----------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index e3b597d933880..626e5e86f4e0c 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -86,18 +86,34 @@ static const struct clk_ops skl_int3472_clock_ops = { .recalc_rate = skl_int3472_clk_recalc_rate, }; -int skl_int3472_register_clock(struct int3472_discrete_device *int3472) +int skl_int3472_register_clock(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio) { + char *path = agpio->resource_source.string_ptr; struct clk_init_data init = { .ops = &skl_int3472_clock_ops, .flags = CLK_GET_RATE_NOCACHE, }; int ret; + if (int3472->clock.cl) + return -EBUSY; + + int3472->clock.ena_gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], + "int3472,clk-enable"); + if (IS_ERR(int3472->clock.ena_gpio)) + return dev_err_probe(int3472->dev, PTR_ERR(int3472->clock.ena_gpio), + "getting clk-enable GPIO\n"); + + /* Ensure the pin is in output mode and non-active state */ + gpiod_direction_output(int3472->clock.ena_gpio, 0); + init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(int3472->adev)); - if (!init.name) - return -ENOMEM; + if (!init.name) { + ret = -ENOMEM; + goto out_put_gpio; + } int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); @@ -123,14 +139,20 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472) clk_unregister(int3472->clock.clk); out_free_init_name: kfree(init.name); +out_put_gpio: + gpiod_put(int3472->clock.ena_gpio); return ret; } void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) { + if (!int3472->clock.cl) + return; + clkdev_drop(int3472->clock.cl); clk_unregister(int3472->clock.clk); + gpiod_put(int3472->clock.ena_gpio); } int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h index 82dc37e08882e..0d4fa7d00b5f6 100644 --- a/drivers/platform/x86/intel/int3472/common.h +++ b/drivers/platform/x86/intel/int3472/common.h @@ -121,7 +121,8 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, struct acpi_device **sensor_adev_ret, const char **name_ret); -int skl_int3472_register_clock(struct int3472_discrete_device *int3472); +int skl_int3472_register_clock(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio); void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 67d26f8a8d9a3..7bac0dd850c83 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -2,8 +2,6 @@ /* Author: Dan Scally */ #include -#include -#include #include #include #include @@ -168,24 +166,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 return 0; } -static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio) -{ - char *path = agpio->resource_source.string_ptr; - u16 pin = agpio->pin_table[0]; - struct gpio_desc *gpio; - - gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - - int3472->clock.ena_gpio = gpio; - /* Ensure the pin is in output mode and non-active state */ - gpiod_direction_output(int3472->clock.ena_gpio, 0); - - return skl_int3472_register_clock(int3472); -} - static void int3472_get_func_and_polarity(u8 type, const char **func, u32 *polarity) { switch (type) { @@ -291,9 +271,9 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_CLK_ENABLE: - ret = skl_int3472_map_gpio_to_clk(int3472, agpio); + ret = skl_int3472_register_clock(int3472, agpio); if (ret) - err_msg = "Failed to map GPIO to clock\n"; + err_msg = "Failed to register clock\n"; break; case INT3472_GPIO_TYPE_PRIVACY_LED: @@ -356,11 +336,7 @@ static int skl_int3472_discrete_remove(struct platform_device *pdev) gpiod_remove_lookup_table(&int3472->gpios); - if (int3472->clock.cl) - skl_int3472_unregister_clock(int3472); - - gpiod_put(int3472->clock.ena_gpio); - + skl_int3472_unregister_clock(int3472); skl_int3472_unregister_pled(int3472); skl_int3472_unregister_regulator(int3472); -- 2.40.0 From cc126cf324325818a40c2a7c0fce33047804d340 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 27 Jan 2023 21:37:29 +0100 Subject: [PATCH] platform/x86: int3472/discrete: Get the polarity from the _DSM entry According to: https://github.com/intel/ipu6-drivers/blob/master/patch/int3472-support-independent-clock-and-LED-gpios-5.17%2B.patch Bits 31-24 of the _DSM pin entry integer value codes the active-value, that is the actual physical signal (0 or 1) which needs to be output on the pin to turn the sensor chip on (to make it active). So if bits 31-24 are 0 for a reset pin, then the actual value of the reset pin needs to be 0 to take the chip out of reset. IOW in this case the reset signal is active-high rather then the default active-low. And if bits 31-24 are 0 for a clk-en pin then the actual value of the clk pin needs to be 0 to enable the clk. So in this case the clk-en signal is active-low rather then the default active-high. IOW if bits 31-24 are 0 for a pin, then the default polarity of the pin is inverted. Add a check for this and also propagate this new polarity to the clock registration. Signed-off-by: Hans de Goede Patchset: cameras --- .../platform/x86/intel/int3472/clk_and_regulator.c | 5 ++++- drivers/platform/x86/intel/int3472/common.h | 2 +- drivers/platform/x86/intel/int3472/discrete.c | 13 +++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index 626e5e86f4e0c..1086c3d834945 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -87,7 +87,7 @@ static const struct clk_ops skl_int3472_clock_ops = { }; int skl_int3472_register_clock(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio) + struct acpi_resource_gpio *agpio, u32 polarity) { char *path = agpio->resource_source.string_ptr; struct clk_init_data init = { @@ -105,6 +105,9 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472, return dev_err_probe(int3472->dev, PTR_ERR(int3472->clock.ena_gpio), "getting clk-enable GPIO\n"); + if (polarity == GPIO_ACTIVE_LOW) + gpiod_toggle_active_low(int3472->clock.ena_gpio); + /* Ensure the pin is in output mode and non-active state */ gpiod_direction_output(int3472->clock.ena_gpio, 0); diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h index 0d4fa7d00b5f6..61688e450ce58 100644 --- a/drivers/platform/x86/intel/int3472/common.h +++ b/drivers/platform/x86/intel/int3472/common.h @@ -122,7 +122,7 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, const char **name_ret); int skl_int3472_register_clock(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio); + struct acpi_resource_gpio *agpio, u32 polarity); void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 7bac0dd850c83..d402289ddaab8 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -234,11 +234,11 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, struct int3472_discrete_device *int3472 = data; struct acpi_resource_gpio *agpio; union acpi_object *obj; + u8 active_value, type; const char *err_msg; const char *func; u32 polarity; int ret; - u8 type; if (!acpi_gpio_get_io_resource(ares, &agpio)) return 1; @@ -262,6 +262,15 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, int3472_get_func_and_polarity(type, &func, &polarity); + /* If bits 31-24 of the _DSM entry are all 0 then the signal is inverted */ + active_value = obj->integer.value >> 24; + if (!active_value) + polarity ^= GPIO_ACTIVE_LOW; + + dev_dbg(int3472->dev, "%s %s pin %d active-%s\n", func, + agpio->resource_source.string_ptr, agpio->pin_table[0], + (polarity == GPIO_ACTIVE_HIGH) ? "high" : "low"); + switch (type) { case INT3472_GPIO_TYPE_RESET: case INT3472_GPIO_TYPE_POWERDOWN: @@ -271,7 +280,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_CLK_ENABLE: - ret = skl_int3472_register_clock(int3472, agpio); + ret = skl_int3472_register_clock(int3472, agpio, polarity); if (ret) err_msg = "Failed to register clock\n"; -- 2.40.0 From 4b6896c6ca8ca93fd912c81130058e96ff4273a9 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:16 +0800 Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED Add MFD cell for tps68470-led. Reviewed-by: Daniel Scally Signed-off-by: Kate Hsuan Patchset: cameras --- drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 6a0ff035cf209..2a7d01d3abc85 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -17,7 +17,7 @@ #define DESIGNED_FOR_CHROMEOS 1 #define DESIGNED_FOR_WINDOWS 2 -#define TPS68470_WIN_MFD_CELL_COUNT 3 +#define TPS68470_WIN_MFD_CELL_COUNT 4 static const struct mfd_cell tps68470_cros[] = { { .name = "tps68470-gpio" }, @@ -200,7 +200,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) cells[1].name = "tps68470-regulator"; cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); - cells[2].name = "tps68470-gpio"; + cells[2].name = "tps68470-led"; + cells[3].name = "tps68470-gpio"; for (i = 0; i < board_data->n_gpiod_lookups; i++) gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); -- 2.40.0 From aa9cf51965d5dd22f00975c1dc00f403cdedb7be Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:17 +0800 Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB (TPS68470_ILEDCTL_ENB), and current control mask for LEDB (TPS68470_ILEDCTL_CTRLB) Reviewed-by: Daniel Scally Reviewed-by: Hans de Goede Signed-off-by: Kate Hsuan Patchset: cameras --- include/linux/mfd/tps68470.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h index 7807fa329db00..2d2abb25b944f 100644 --- a/include/linux/mfd/tps68470.h +++ b/include/linux/mfd/tps68470.h @@ -34,6 +34,7 @@ #define TPS68470_REG_SGPO 0x22 #define TPS68470_REG_GPDI 0x26 #define TPS68470_REG_GPDO 0x27 +#define TPS68470_REG_ILEDCTL 0x28 #define TPS68470_REG_VCMVAL 0x3C #define TPS68470_REG_VAUX1VAL 0x3D #define TPS68470_REG_VAUX2VAL 0x3E @@ -94,4 +95,8 @@ #define TPS68470_GPIO_MODE_OUT_CMOS 2 #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 +#define TPS68470_ILEDCTL_ENA BIT(2) +#define TPS68470_ILEDCTL_ENB BIT(6) +#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) + #endif /* __LINUX_MFD_TPS68470_H */ -- 2.40.0 From 222a58980906a6145196331409ef25155aa93703 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Tue, 21 Mar 2023 23:37:18 +0800 Subject: [PATCH] leds: tps68470: Add LED control for tps68470 There are two LED controllers, LEDA indicator LED and LEDB flash LED for tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, tps68470 provides four levels of power status for LEDB. If the properties called "ti,ledb-current" can be found, the current will be set according to the property values. These two LEDs can be controlled through the LED class of sysfs (tps68470-leda and tps68470-ledb). Signed-off-by: Kate Hsuan Patchset: cameras --- drivers/leds/Kconfig | 12 +++ drivers/leds/Makefile | 1 + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 drivers/leds/leds-tps68470.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 499d0f215a8bf..f0caddb6ab757 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -846,6 +846,18 @@ config LEDS_TPS6105X It is a single boost converter primarily for white LEDs and audio amplifiers. +config LEDS_TPS68470 + tristate "LED support for TI TPS68470" + depends on LEDS_CLASS + depends on INTEL_SKL_INT3472 + help + This driver supports TPS68470 PMIC with LED chip. + It provides two LED controllers, with the ability to drive 2 + indicator LEDs and 2 flash LEDs. + + To compile this driver as a module, choose M and it will be + called leds-tps68470 + config LEDS_IP30 tristate "LED support for SGI Octane machines" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4fd2f92cd1981..b381220400398 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o +obj-$(CONFIG_LEDS_TPS68470) += leds-tps68470.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c new file mode 100644 index 0000000000000..35aeb5db89c8f --- /dev/null +++ b/drivers/leds/leds-tps68470.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED driver for TPS68470 PMIC + * + * Copyright (C) 2023 Red Hat + * + * Authors: + * Kate Hsuan + */ + +#include +#include +#include +#include +#include +#include + + +#define lcdev_to_led(led_cdev) \ + container_of(led_cdev, struct tps68470_led, lcdev) + +#define led_to_tps68470(led, index) \ + container_of(led, struct tps68470_device, leds[index]) + +enum tps68470_led_ids { + TPS68470_ILED_A, + TPS68470_ILED_B, + TPS68470_NUM_LEDS +}; + +static const char *tps68470_led_names[] = { + [TPS68470_ILED_A] = "tps68470-iled_a", + [TPS68470_ILED_B] = "tps68470-iled_b", +}; + +struct tps68470_led { + unsigned int led_id; + struct led_classdev lcdev; +}; + +struct tps68470_device { + struct device *dev; + struct regmap *regmap; + struct tps68470_led leds[TPS68470_NUM_LEDS]; +}; + +enum ctrlb_current { + CTRLB_2MA = 0, + CTRLB_4MA = 1, + CTRLB_8MA = 2, + CTRLB_16MA = 3, +}; + +static int tps68470_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + + switch (led->led_id) { + case TPS68470_ILED_A: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENA, + brightness ? TPS68470_ILEDCTL_ENA : 0); + case TPS68470_ILED_B: + return regmap_update_bits(regmap, TPS68470_REG_ILEDCTL, TPS68470_ILEDCTL_ENB, + brightness ? TPS68470_ILEDCTL_ENB : 0); + } + return -EINVAL; +} + +static enum led_brightness tps68470_brightness_get(struct led_classdev *led_cdev) +{ + struct tps68470_led *led = lcdev_to_led(led_cdev); + struct tps68470_device *tps68470 = led_to_tps68470(led, led->led_id); + struct regmap *regmap = tps68470->regmap; + int ret = 0; + int value = 0; + + ret = regmap_read(regmap, TPS68470_REG_ILEDCTL, &value); + if (ret) + return dev_err_probe(led_cdev->dev, -EINVAL, "failed on reading register\n"); + + switch (led->led_id) { + case TPS68470_ILED_A: + value = value & TPS68470_ILEDCTL_ENA; + break; + case TPS68470_ILED_B: + value = value & TPS68470_ILEDCTL_ENB; + break; + } + + return value ? LED_ON : LED_OFF; +} + + +static int tps68470_ledb_current_init(struct platform_device *pdev, + struct tps68470_device *tps68470) +{ + int ret = 0; + unsigned int curr; + + /* configure LEDB current if the properties can be got */ + if (!device_property_read_u32(&pdev->dev, "ti,ledb-current", &curr)) { + if (curr > CTRLB_16MA) { + dev_err(&pdev->dev, + "Invalid LEDB current value: %d\n", + curr); + return -EINVAL; + } + ret = regmap_update_bits(tps68470->regmap, TPS68470_REG_ILEDCTL, + TPS68470_ILEDCTL_CTRLB, curr); + } + return ret; +} + +static int tps68470_leds_probe(struct platform_device *pdev) +{ + int i = 0; + int ret = 0; + struct tps68470_device *tps68470; + struct tps68470_led *led; + struct led_classdev *lcdev; + + tps68470 = devm_kzalloc(&pdev->dev, sizeof(struct tps68470_device), + GFP_KERNEL); + if (!tps68470) + return -ENOMEM; + + tps68470->dev = &pdev->dev; + tps68470->regmap = dev_get_drvdata(pdev->dev.parent); + + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + led = &tps68470->leds[i]; + lcdev = &led->lcdev; + + led->led_id = i; + + lcdev->name = devm_kasprintf(tps68470->dev, GFP_KERNEL, "%s::%s", + tps68470_led_names[i], LED_FUNCTION_INDICATOR); + if (!lcdev->name) + return -ENOMEM; + + lcdev->max_brightness = 1; + lcdev->brightness = 0; + lcdev->brightness_set_blocking = tps68470_brightness_set; + lcdev->brightness_get = tps68470_brightness_get; + lcdev->dev = &pdev->dev; + + ret = devm_led_classdev_register(tps68470->dev, lcdev); + if (ret) { + dev_err_probe(tps68470->dev, ret, + "error registering led\n"); + goto err_exit; + } + + if (i == TPS68470_ILED_B) { + ret = tps68470_ledb_current_init(pdev, tps68470); + if (ret) + goto err_exit; + } + } + +err_exit: + if (ret) { + for (i = 0; i < TPS68470_NUM_LEDS; i++) { + if (tps68470->leds[i].lcdev.name) + devm_led_classdev_unregister(&pdev->dev, + &tps68470->leds[i].lcdev); + } + } + + return ret; +} +static struct platform_driver tps68470_led_driver = { + .driver = { + .name = "tps68470-led", + }, + .probe = tps68470_leds_probe, +}; + +module_platform_driver(tps68470_led_driver); + +MODULE_ALIAS("platform:tps68470-led"); +MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); -- 2.40.0 From 938ba934aa628f28cb249ce1d32d77d6b44b4c3d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 22 Mar 2023 11:01:42 +0000 Subject: [PATCH] media: v4l2-core: Acquire privacy led in v4l2_async_register_subdev() The current call to v4l2_subdev_get_privacy_led() is contained in v4l2_async_register_subdev_sensor(), but that function isn't used by all the sensor drivers. Move the acquisition of the privacy led to v4l2_async_register_subdev() instead. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/v4l2-core/v4l2-async.c | 4 ++++ drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index b26dcb8d423e1..43774d5acf5d1 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -757,6 +757,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) struct v4l2_async_notifier *notifier; int ret; + ret = v4l2_subdev_get_privacy_led(sd); + if (ret < 0) + return ret; + /* * No reference taken. The reference is held by the device * (struct v4l2_subdev.dev), and async sub-device does not diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c index 8501b6931d3a0..077d85553b2b9 100644 --- a/drivers/media/v4l2-core/v4l2-fwnode.c +++ b/drivers/media/v4l2-core/v4l2-fwnode.c @@ -1303,10 +1303,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) v4l2_async_nf_init(notifier); - ret = v4l2_subdev_get_privacy_led(sd); - if (ret < 0) - goto out_cleanup; - ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); if (ret < 0) goto out_cleanup; -- 2.40.0 From 00815059922465c2f29aceccff0e0a19e8a3f185 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 21 Mar 2023 13:45:26 +0000 Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 Update the control ID for the gain control in the ov7251 driver to V4L2_CID_ANALOGUE_GAIN. Signed-off-by: Daniel Scally Patchset: cameras --- drivers/media/i2c/ov7251.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 88e9874352853..ff7b2c26da835 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_EXPOSURE: ret = ov7251_set_exposure(ov7251, ctrl->val); break; - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = ov7251_set_gain(ov7251, ctrl->val); break; case V4L2_CID_TEST_PATTERN: @@ -1551,7 +1551,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_EXPOSURE, 1, 32, 1, 32); ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_GAIN, 16, 1023, 1, 16); + V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov7251_test_pattern_menu) - 1, -- 2.40.0