// SPDX-License-Identifier: GPL-2.0 /* * PCIe Enclosure management driver created for LED interfaces based on * indications. It says *what indications* blink but does not specify *how* * they blink - it is hardware defined. * * The driver name refers to Native PCIe Enclosure Management. It is * first indication oriented standard with specification. * * Native PCIe Enclosure Management (NPEM) * PCIe Base Specification r6.1 sec 6.28, 7.9.19 * * _DSM Definitions for PCIe SSD Status LED * PCI Firmware Specification, r3.3 sec 4.7 * * Two backends are supported to manipulate indications: Direct NPEM register * access (npem_ops) and indirect access through the ACPI _DSM (dsm_ops). * _DSM is used if supported, else NPEM. * * Copyright (c) 2021-2022 Dell Inc. * Copyright (c) 2023-2024 Intel Corporation * Mariusz Tkaczyk <[email protected]> */ #include <linux/acpi.h> #include <linux/bitops.h> #include <linux/errno.h> #include <linux/iopoll.h> #include <linux/leds.h> #include <linux/mutex.h> #include <linux/pci.h> #include <linux/pci_regs.h> #include <linux/types.h> #include <linux/uleds.h> #include "pci.h" struct indication { … }; static const struct indication npem_indications[] = …; /* _DSM PCIe SSD LED States correspond to NPEM register values */ static const struct indication dsm_indications[] = …; #define for_each_indication(ind, inds) … /* * The driver has internal list of supported indications. Ideally, the driver * should not touch bits that are not defined and for which LED devices are * not exposed but in reality, it needs to turn them off. * * Otherwise, there will be no possibility to turn off indications turned on by * other utilities or turned on by default and it leads to bad user experience. * * Additionally, it excludes NPEM commands like RESET or ENABLE. */ static u32 reg_to_indications(u32 caps, const struct indication *inds) { … } /** * struct npem_led - LED details * @indication: indication details * @npem: NPEM device * @name: LED name * @led: LED device */ struct npem_led { … }; /** * struct npem_ops - backend specific callbacks * @get_active_indications: get active indications * npem: NPEM device * inds: response buffer * @set_active_indications: set new indications * npem: npem device * inds: bit mask to set * @inds: supported indications array, set of indications is backend specific * @name: backend name */ struct npem_ops { … }; /** * struct npem - NPEM device properties * @dev: PCI device this driver is attached to * @ops: backend specific callbacks * @lock: serializes concurrent access to NPEM device by multiple LED devices * @pos: cached offset of NPEM Capability Register in Configuration Space; * only used if NPEM registers are accessed directly and not through _DSM * @supported_indications: cached bit mask of supported indications; * non-indication and reserved bits in the NPEM Capability Register are * cleared in this bit mask * @active_indications: cached bit mask of active indications; * non-indication and reserved bits in the NPEM Control Register are * cleared in this bit mask * @active_inds_initialized: whether @active_indications has been initialized; * On Dell platforms, it is required that IPMI drivers are loaded before * the GET_STATE_DSM method is invoked: They use an IPMI OpRegion to * get/set the active LEDs. By initializing @active_indications lazily * (on first access to an LED), IPMI drivers are given a chance to load. * If they are not loaded in time, users will see various errors on LED * access in dmesg. Once they are loaded, the errors go away and LED * access becomes possible. * @led_cnt: size of @leds array * @leds: array containing LED class devices of all supported LEDs */ struct npem { … }; static int npem_read_reg(struct npem *npem, u16 reg, u32 *val) { … } static int npem_write_ctrl(struct npem *npem, u32 reg) { … } static int npem_get_active_indications(struct npem *npem, u32 *inds) { … } static int npem_set_active_indications(struct npem *npem, u32 inds) { … } static const struct npem_ops npem_ops = …; #define DSM_GUID … #define GET_SUPPORTED_STATES_DSM … #define GET_STATE_DSM … #define SET_STATE_DSM … static const guid_t dsm_guid = …; static bool npem_has_dsm(struct pci_dev *pdev) { … } struct dsm_output { … }; /** * dsm_evaluate() - send DSM PCIe SSD Status LED command * @pdev: PCI device * @dsm_func: DSM LED Function * @output: buffer to copy DSM Response * @value_to_set: value for SET_STATE_DSM function * * To not bother caller with ACPI context, the returned _DSM Output Buffer is * copied. */ static int dsm_evaluate(struct pci_dev *pdev, u64 dsm_func, struct dsm_output *output, u32 value_to_set) { … } static int dsm_get(struct pci_dev *pdev, u64 dsm_func, u32 *buf) { … } static int dsm_get_active_indications(struct npem *npem, u32 *buf) { … } static int dsm_set_active_indications(struct npem *npem, u32 value) { … } static const struct npem_ops dsm_ops = …; static int npem_initialize_active_indications(struct npem *npem) { … } /* * The status of each indicator is cached on first brightness_ get/set time * and updated at write time. brightness_get() is only responsible for * reflecting the last written/cached value. */ static enum led_brightness brightness_get(struct led_classdev *led) { … } static int brightness_set(struct led_classdev *led, enum led_brightness brightness) { … } static void npem_free(struct npem *npem) { … } static int pci_npem_set_led_classdev(struct npem *npem, struct npem_led *nled) { … } static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops, int pos, u32 caps) { … } void pci_npem_remove(struct pci_dev *dev) { … } void pci_npem_create(struct pci_dev *dev) { … }