OpenCloudOS-Kernel/drivers/leds/leds-altra-spci.c

441 lines
12 KiB
C

/* LEDs driver for Altra SPCI
*
* Copyright (C) 2020 Ampere Computing LLC
* Author: Dung Cao <dcao@os.amperecompting.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* This driver support control LED via SPCI interface on Altra system.
* Currently, it is 2-LEDs system(Activity LED, Status LED). Activity LED is
* completely controlled by hardware. Status LED is controlled by this driver.
* There are 4 stages of Status LED:
* - ON
* - OFF
* - Locate(blink at 1Hz)
* - Rebuild(blink at 4Hz)
* Example:
* To On, Off the LED on slot 9:
* 1. Write the seg:bus_number corresponding to LED on slot 9 to altra:led
* echo 000b:03 > /sys/class/leds/altra:led/address
* 2. Write 1 to brightness attr to ON, 0 to brightness attr to OFF
* echo 1 > /sys/class/leds/altra:led/brightness
* echo 0 > /sys/class/leds/altra:led/brightness
* To blink the LED on slot 9:
* 1. Write the seg:bus_number corresponding to LED on slot 9 to altra:led
* echo 000b:03 > /sys/class/leds/altra:led/address
* 2. Write 1 to blink attr to select Locate(blink 1Hz), or 4 to blink attr
* to select Rebuild(blink 4Hz)
* echo 1 > /sys/class/leds/altra:led/blink
* echo 4 > /sys/class/leds/altra:led/blink
* 3. Write 1 to shot attr to execute blink
* echo 1 > /sys/class/leds/altra:led/shot
*/
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/arm-smccc.h>
#include <linux/types.h>
#include <uapi/linux/uleds.h>
#include <linux/bitfield.h>
/* LED miscellaneous functions */
#define LED_CMD 4 /* Control LED state */
#define GETPORT_CMD 9 /* Get PCIE port */
#define LED_FAULT 1 /* LED type - Fault */
#define LED_ATT 2 /* LED type - Attention */
#define LED_SET_ON 1
#define LED_SET_OFF 2
#define LED_SET_BLINK_1HZ 3
#define LED_SET_BLINK_4HZ 4
#define BLINK_1HZ 1
#define BLINK_4HZ 4
#define ARG_SEG_MASK GENMASK(3,0)
#define ARG_BUS_MASK GENMASK(7,4)
/* Function id for SMC calling convention */
#define FUNCID_TYPE_SHIFT 31
#define FUNCID_CC_SHIFT 30
#define FUNCID_OEN_SHIFT 24
#define FUNCID_NUM_SHIFT 0
#define FUNCID_TYPE_MASK 0x1
#define FUNCID_CC_MASK 0x1
#define FUNCID_OEN_MASK 0x3f
#define FUNCID_NUM_MASK 0xffff
#define FUNCID_TYPE_WIDTH 1
#define FUNCID_CC_WIDTH 1
#define FUNCID_OEN_WIDTH 6
#define FUNCID_NUM_WIDTH 16
#define SMC_64 1
#define SMC_32 0
#define SMC_TYPE_FAST 1
#define SMC_TYPE_STD 0
/* SPCI error codes. */
#define SPCI_SUCCESS 0
#define SPCI_NOT_SUPPORTED -1
#define SPCI_INVALID_PARAMETER -2
#define SPCI_NO_MEMORY -3
#define SPCI_BUSY -4
#define SPCI_QUEUED -5
#define SPCI_DENIED -6
#define SPCI_NOT_PRESENT -7
/* Client ID used for SPCI calls */
#define SPCI_CLIENT_ID 0x0000ACAC
/* This error code must be different to the ones used by SPCI */
#define SPCI_ERROR -42
/* Definitions to build the complete SMC ID */
#define SPCI_FID_MISC_FLAG (0 << 27)
#define SPCI_FID_MISC_SHIFT 20
#define SPCI_FID_MISC_MASK 0x7F
#define SPCI_FID_TUN_FLAG BIT(27)
#define SPCI_FID_TUN_SHIFT 24
#define SPCI_FID_TUN_MASK 0x7
#define OEN_SPCI_START 0x30
#define OEN_SPCI_END 0x3F
#define ARG_RES_MASK GENMASK(15,0)
#define ARG_HANDLE_MASK GENMASK(31,16)
#define SPCI_SMC(spci_fid) ((OEN_SPCI_START << FUNCID_OEN_SHIFT) | \
(1 << 31) | (spci_fid))
#define SPCI_MISC_32(misc_fid) ((SMC_32 << FUNCID_CC_SHIFT) | \
SPCI_FID_MISC_FLAG | \
SPCI_SMC((misc_fid) << SPCI_FID_MISC_SHIFT))
#define SPCI_MISC_64(misc_fid) ((SMC_64 << FUNCID_CC_SHIFT) | \
SPCI_FID_MISC_FLAG | \
SPCI_SMC((misc_fid) << SPCI_FID_MISC_SHIFT))
#define SPCI_TUN_32(tun_fid) ((SMC_32 << FUNCID_CC_SHIFT) | \
SPCI_FID_TUN_FLAG | \
SPCI_SMC((tun_fid) << SPCI_FID_TUN_SHIFT))
#define SPCI_TUN_64(tun_fid) ((SMC_64 << FUNCID_CC_SHIFT) | \
SPCI_FID_TUN_FLAG | \
SPCI_SMC((tun_fid) << SPCI_FID_TUN_SHIFT))
/* SPCI miscellaneous functions */
#define SPCI_FID_VERSION 0x0
#define SPCI_FID_SERVICE_HANDLE_OPEN 0x2
#define SPCI_FID_SERVICE_HANDLE_CLOSE 0x3
#define SPCI_FID_SERVICE_MEM_REGISTER 0x4
#define SPCI_FID_SERVICE_MEM_UNREGISTER 0x5
#define SPCI_FID_SERVICE_MEM_PUBLISH 0x6
#define SPCI_FID_SERVICE_REQUEST_BLOCKING 0x7
#define SPCI_FID_SERVICE_REQUEST_START 0x8
#define SPCI_FID_SERVICE_GET_RESPONSE 0x9
#define SPCI_FID_SERVICE_RESET_CLIENT_STATE 0xA
/* SPCI tunneling functions */
#define SPCI_FID_SERVICE_TUN_REQUEST_START 0x0
#define SPCI_FID_SERVICE_REQUEST_RESUME 0x1
#define SPCI_FID_SERVICE_TUN_REQUEST_BLOCKING 0x2
/* Complete SMC IDs and associated values */
#define SPCI_VERSION SPCI_MISC_32(SPCI_FID_VERSION)
#define SPCI_SERVICE_HANDLE_OPEN \
SPCI_MISC_32(SPCI_FID_SERVICE_HANDLE_OPEN)
#define SPCI_SERVICE_HANDLE_OPEN_NOTIFY_BIT 1
#define SPCI_SERVICE_HANDLE_CLOSE \
SPCI_MISC_32(SPCI_FID_SERVICE_HANDLE_CLOSE)
#define SPCI_SERVICE_REQUEST_BLOCKING_AARCH32 \
SPCI_MISC_32(SPCI_FID_SERVICE_REQUEST_BLOCKING)
#define SPCI_SERVICE_REQUEST_BLOCKING_AARCH64 \
SPCI_MISC_64(SPCI_FID_SERVICE_REQUEST_BLOCKING)
struct spci_led {
struct led_classdev cdev;
u8 seg;
u8 bus_num;
u8 blink;
u32 uuid[4];
};
static int spci_service_handle_open(u16 client_id, u16 *handle,
u32 uuid1, u32 uuid2, u32 uuid3, u32 uuid4)
{
int ret;
struct arm_smccc_res res;
u32 x1;
arm_smccc_smc(SPCI_SERVICE_HANDLE_OPEN, uuid1, uuid2, uuid3, uuid4,
0, 0, client_id, &res);
ret = res.a0;
if (ret != SPCI_SUCCESS) {
return ret;
}
x1 = res.a1;
if (FIELD_GET(ARG_RES_MASK, x1) != 0) {
return SPCI_ERROR;
}
/* The handle is returned in the top 16 bits */
*handle = FIELD_GET(ARG_HANDLE_MASK, x1);
return SPCI_SUCCESS;
}
static int spci_service_handle_close(u16 client_id, u16 handle)
{
struct arm_smccc_res res;
arm_smccc_smc(SPCI_SERVICE_HANDLE_CLOSE, (client_id | (handle << 16)),
0, 0, 0, 0, 0, 0, &res);
return (int) res.a0;
}
static int spci_service_request_locking(u64 x1, u64 x2, u64 x3, u64 x4,
u64 x5, u64 x6, u16 client_id, u16 handle,
u64 *rx1, u64 *rx2, u64 *rx3)
{
struct arm_smccc_res res;
int ret;
arm_smccc_smc(SPCI_SERVICE_REQUEST_BLOCKING_AARCH64, x1, x2, x3, x4,
x5, x6, (client_id | (handle << 16)), &res);
ret = res.a0;
if (ret != SPCI_SUCCESS)
return ret;
if (rx1 != NULL)
*rx1 = res.a1;
if (rx2 != NULL)
*rx2 = res.a2;
if (rx2 != NULL)
*rx3 = res.a3;
return SPCI_SUCCESS;
}
static const struct acpi_device_id altra_spci_leds_acpi_match[] = {
{"AMPC0008", 1},
{ },
};
MODULE_DEVICE_TABLE(acpi, altra_spci_leds_acpi_match);
static void spci_led_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct spci_led *led = container_of(cdev, struct spci_led, cdev);
u64 x1, x2, x3;
u16 handle;
int ret;
if (!led) {
dev_err(cdev->dev, "Failed to get resource\n");
return;
}
ret = spci_service_handle_open(SPCI_CLIENT_ID, &handle,
led->uuid[0], led->uuid[1],
led->uuid[2], led->uuid[3]);
if (ret != SPCI_SUCCESS) {
dev_err(cdev->dev, "spci_service_handle_open has failure %d\n", ret);
return;
}
x1 = value ? LED_SET_ON : LED_SET_OFF;
x2 = LED_ATT;
x3 = FIELD_PREP(ARG_SEG_MASK, led->seg) |
FIELD_PREP(ARG_BUS_MASK, led->bus_num);
ret = spci_service_request_locking(LED_CMD, x1, x2,
x3, 0, 0, SPCI_CLIENT_ID, handle,
NULL, NULL, NULL);
if (ret != SPCI_SUCCESS) {
dev_err(cdev->dev, "spci_service_request_locking has failure %d\n", ret);
return;
}
ret = spci_service_handle_close(SPCI_CLIENT_ID, handle);
if (ret != SPCI_SUCCESS) {
dev_err(cdev->dev, "spci_service_handle_close has failure %d\n", ret);
return;
}
}
static ssize_t led_shot(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct spci_led *led = dev_get_drvdata(dev);
u64 x1, x2, x3;
u16 handle;
int ret;
ret = spci_service_handle_open(SPCI_CLIENT_ID, &handle,
led->uuid[0], led->uuid[1],
led->uuid[2], led->uuid[3]);
if (ret != SPCI_SUCCESS) {
dev_err(dev, "spci_service_handle_open has failure %d\n", ret);
return ret;
}
if (led->blink == BLINK_1HZ)
x1 = LED_SET_BLINK_1HZ;
else if (led->blink == BLINK_4HZ)
x1 = LED_SET_BLINK_4HZ;
else {
dev_err(dev, "Wrong blink set mode\n");
return -EINVAL;
}
x2 = LED_ATT;
x3 = FIELD_PREP(ARG_SEG_MASK, led->seg) |
FIELD_PREP(ARG_BUS_MASK, led->bus_num);
ret = spci_service_request_locking(LED_CMD, x1, x2,
x3, 0, 0, SPCI_CLIENT_ID, handle,
NULL, NULL, NULL);
if (ret != SPCI_SUCCESS) {
dev_err(dev, "spci_service_request_locking has failure %d\n", ret);
return ret;
}
ret = spci_service_handle_close(SPCI_CLIENT_ID, handle);
if (ret != SPCI_SUCCESS) {
dev_err(dev, "spci_service_handle_close has failure %d\n", ret);
return ret;
}
return size;
}
static ssize_t led_blink_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spci_led *led = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", led->blink);
}
static ssize_t led_blink_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct spci_led *led = dev_get_drvdata(dev);
unsigned long state;
int ret;
ret = kstrtoul(buf, 0, &state);
if (ret)
return ret;
led->blink = state;
return size;
}
static ssize_t led_address_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spci_led *led = dev_get_drvdata(dev);
return sprintf(buf, "%04x:%02u\n", led->seg, led->bus_num);
}
static ssize_t led_address_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct spci_led *led = dev_get_drvdata(dev);
char *t, *q;
char m[16];
unsigned long seg, bus_num;
strcpy(m, buf);
q = m;
t = strchr(q, ':');
if (t == NULL) {
dev_err(dev, "Invalid address format %s\n", q);
return size;
}
*t = '\0';
if (kstrtoul(q, 16, &seg)) {
dev_err(dev, "Fail to convert string %s to unsigned\n", q);
return size;
}
q = t + 1;
if (kstrtoul(q, 16, &bus_num)) {
dev_err(dev, "Fail to convert string %s to unsigned\n", q);
return size;
}
led->seg = seg;
led->bus_num = bus_num;
return size;
}
static DEVICE_ATTR(blink, 0644, led_blink_show, led_blink_store);
static DEVICE_ATTR(address, 0644, led_address_show, led_address_store);
static DEVICE_ATTR(shot, 0200, NULL, led_shot);
static int spci_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *fwnode = NULL;
struct spci_led *led_data;
char name[LED_MAX_NAME_SIZE];
int ret = 0;
led_data = devm_kzalloc(dev, sizeof(struct spci_led), GFP_KERNEL);
if (led_data == NULL)
return -ENOMEM;
fwnode = acpi_fwnode_handle(ACPI_COMPANION(dev));
ret = fwnode_property_read_u32_array(fwnode, "uuid", led_data->uuid, 4);
if (ret) {
dev_err(dev, "Failed to get uuid\n");
return ret;
}
snprintf(name, LED_MAX_NAME_SIZE, "altra:led");
led_data->cdev.name = name;
led_data->cdev.brightness_set = spci_led_set;
ret = devm_led_classdev_register(dev, &led_data->cdev);
if (ret < 0) {
dev_err(dev, "Failed to register %s: %d\n", name, ret);
return ret;
}
dev_set_drvdata(led_data->cdev.dev, led_data);
if (device_create_file(led_data->cdev.dev, &dev_attr_shot))
device_remove_file(led_data->cdev.dev, &dev_attr_shot);
if (device_create_file(led_data->cdev.dev, &dev_attr_blink))
device_remove_file(led_data->cdev.dev, &dev_attr_blink);
if (device_create_file(led_data->cdev.dev, &dev_attr_address))
device_remove_file(led_data->cdev.dev, &dev_attr_address);
dev_info(dev, "Altra SPCI LED driver probed\n");
return 0;
}
static struct platform_driver spci_led_driver = {
.probe = spci_led_probe,
.driver = {
.name = "leds-altra-spci",
.acpi_match_table = ACPI_PTR(altra_spci_leds_acpi_match),
},
};
module_platform_driver(spci_led_driver);
MODULE_AUTHOR("Dung Cao <dcao@os.amperecompting.com>");
MODULE_DESCRIPTION("Altra SPCI LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-spci");