sonic-buildimage/platform/broadcom/sonic-platform-modules-juniper/common/modules/jnx-refpga-tmc.c

604 lines
14 KiB
C
Raw Normal View History

/*
* Juniper Networks RE-FPGA qfx platform specific driver
*
* Copyright (C) 2020 Juniper Networks
* Author: Ciju Rajan K <crajank@juniper.net>
*
* This driver implements various features such as
* - ALARM led driver
* - Fan full speed reset control
* - FAN precense detection
* - FAN type detection
* - Any new QFX specific features which uses RE-FPGA
*
* 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#define NUM_LEDS 7 /* Max number of Alarm + FAN LEDs */
#define ALARM_MINOR_LED 0
#define ALARM_MAJOR_LED 1
#define REFPGA_PCIE_RESET_CTRL 0x13
#define REFPGA_PCIE_ALARM 0x33
#define REFPGA_FAN0_CTRL_STAT 0x28
#define REFPGA_RESET_FAN_SPEED BIT(3)
#define REFPGA_OPER_TYPE BIT(0)
#define REFPGA_OPER_START BIT(1)
#define REFPGA_OPER_DONE BIT(2)
#define TMC_REFPGA_ADDR_REG 0x0 /* TMC offset: 0x228 */
#define TMC_REFPGA_DATA_REG 0x4 /* TMC offset: 0x22C */
#define TMC_REFPGA_CTRL_REG 0x8 /* TMC offset: 0x230 */
#define TMC_REFPGA_READ_CMD 0x3
#define TMC_REFPGA_WRITE_CMD 0x2
#define REFPGA_INTR_NR_GROUPS 1
#define REFPGA_INTR_MAX_IRQS_PG 32
#define MAX_FANS 5
#define REFPGA_IRQ_MAX_BITS_PER_REG 32
#define POLL_INTERVAL 5000
#define AFI_MASK (0x01)
#define AFO_MASK (0x02)
#define AFI_AFO_MASK (0x03)
/*
* LED specific data structures
*/
struct refpga_led {
struct led_classdev lc;
struct work_struct work;
int blink;
int on;
int bit;
void __iomem *addr;
};
struct refpga_led_data {
int num_leds;
struct refpga_led *leds;
};
static DEFINE_MUTEX(alarm_led_lock);
/*
* Common routines
*/
struct refpga_chip {
struct refpga_led_data *led;
};
static struct refpga_chip *refpga;
static DEFINE_MUTEX(refpga_lock);
static void __iomem *tmc_membase;
static void wait_for_refpga_oper(void __iomem *base_addr)
{
volatile u32 done = ~(-1);
unsigned long int timeout;
void __iomem *addr;
addr = base_addr + (TMC_REFPGA_CTRL_REG);
/*
* Wait till the transaction is complete
*/
timeout = jiffies + msecs_to_jiffies(100);
do {
usleep_range(50, 100);
done = ioread32(addr);
if (done & (REFPGA_OPER_DONE))
break;
} while(time_before(jiffies, timeout));
}
static u32 refpga_read(void __iomem *base_addr, u32 refpga_offset)
{
u32 value;
mutex_lock(&refpga_lock);
iowrite32(refpga_offset, base_addr + (TMC_REFPGA_ADDR_REG));
iowrite32(TMC_REFPGA_READ_CMD, base_addr + (TMC_REFPGA_CTRL_REG));
wait_for_refpga_oper(base_addr);
value = ioread32(base_addr + (TMC_REFPGA_DATA_REG));
mutex_unlock(&refpga_lock);
return value;
}
static void refpga_write(void __iomem *base_addr, u32 refpga_offset, u32 val)
{
mutex_lock(&refpga_lock);
iowrite32(refpga_offset, base_addr + (TMC_REFPGA_ADDR_REG));
iowrite32(val, base_addr + (TMC_REFPGA_DATA_REG));
iowrite32(TMC_REFPGA_WRITE_CMD, base_addr + (TMC_REFPGA_CTRL_REG));
wait_for_refpga_oper(base_addr);
mutex_unlock(&refpga_lock);
}
static bool get_fan_presense(u8 idx)
{
u8 value = 0x00;
u8 offset = REFPGA_FAN0_CTRL_STAT;
bool ret = 0;
value = refpga_read(tmc_membase, (offset + (idx * 2)));
/*
* Get the last two bits of REFPGA_FANx_CTRL_STAT.
* REFPGA_FANx_CTRL_STAT register of REFPGA gives the fan airflow
* status. There are 5 fans in QFX5200. Last two bits give the AFI
* & AFO status. If any of these bits are set, fan is present.
*/
value = (value & BIT(0)) | (value & BIT(1));
if (value)
ret = 1;
return ret;
}
static int get_fan_type(u8 idx)
{
u8 value = 0x00;
u8 offset = REFPGA_FAN0_CTRL_STAT;
int ret = -1;
value = refpga_read(tmc_membase, (offset + (idx * 2)));
/*
* Get the last two bits of REFPGA_FANx_CTRL_STAT.
* REFPGA_FANx_CTRL_STAT register of REFPGA gives the fan airflow
* status. There are 5 fans in QFX5200. Last two bits give the AFI
* & AFO status. If bit1 is set, it's AFO and if bit 0 is set,
* it's AFI.
*
* This function will return '1' for AFO, '0' for AFI, and '-1'
* if there is no fan or if both AFI & AFO bits are set.
*/
value &= AFI_AFO_MASK;
switch(value) {
case AFI_MASK:
ret = 0;
break;
case AFO_MASK:
ret = 1;
break;
default:
ret = -1;
break;
};
return ret;
}
enum sysfs_fan_attributes {
FAN0_PRESENT,
FAN1_PRESENT,
FAN2_PRESENT,
FAN3_PRESENT,
FAN4_PRESENT,
};
enum sysfs_fan_type_attributes {
FAN0_TYPE,
FAN1_TYPE,
FAN2_TYPE,
FAN3_TYPE,
FAN4_TYPE,
};
/*
* The sysfs files will be present in this path
* /sys/devices/pci0000:00/0000:00:1c.0/0000:0f:00.0/refpga-tmc.15/fan*_present
* /sys/devices/pci0000:00/0000:00:1c.0/0000:0f:00.0/refpga-tmc.15/fan*_type
*/
#define DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(index) \
static SENSOR_DEVICE_ATTR(fan##index##_present, S_IRUGO, refpga_fan_presense_show, NULL, FAN##index##_PRESENT)
#define DECLARE_FAN_PRESENT_ATTR(index) &sensor_dev_attr_fan##index##_present.dev_attr.attr
#define DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(index) \
static SENSOR_DEVICE_ATTR(fan##index##_type, S_IRUGO, refpga_fan_type_show, NULL, FAN##index##_TYPE)
#define DECLARE_FAN_TYPE_ATTR(index) &sensor_dev_attr_fan##index##_type.dev_attr.attr
static ssize_t refpga_fan_presense_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr);
return sprintf(buf, "%d\n", get_fan_presense(s_attr->index));
}
static ssize_t refpga_fan_type_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute *s_attr = to_sensor_dev_attr(attr);
return sprintf(buf, "%d\n", get_fan_type(s_attr->index));
}
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(0);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(1);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(2);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(3);
DECLARE_FAN_PRESENT_SENSOR_DEV_ATTR(4);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(0);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(1);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(2);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(3);
DECLARE_FAN_TYPE_SENSOR_DEV_ATTR(4);
static struct attribute *refpga_fan_attrs[] = {
DECLARE_FAN_PRESENT_ATTR(0),
DECLARE_FAN_PRESENT_ATTR(1),
DECLARE_FAN_PRESENT_ATTR(2),
DECLARE_FAN_PRESENT_ATTR(3),
DECLARE_FAN_PRESENT_ATTR(4),
DECLARE_FAN_TYPE_ATTR(0),
DECLARE_FAN_TYPE_ATTR(1),
DECLARE_FAN_TYPE_ATTR(2),
DECLARE_FAN_TYPE_ATTR(3),
DECLARE_FAN_TYPE_ATTR(4),
NULL
};
static struct attribute_group refpga_fan_attr_group = {
.attrs = refpga_fan_attrs,
};
/*
* There is only a single ALARM led in QFX5200 and that
* is used for both Major & Minor alarm indicator.
* These are represented by two different bits in RE-FPGA
* PCIE_ALARM register. Only one of the bit (either Red or
* Yellow) should be set a time. If both the bits are set,
* it's an undefined behaviour.
*
* The following table describes how the conditions are
* handled in the driver as there can be both Major & Minor
* alarms can be triggered from userspace.
*
* Major Minor Colour
*
* 0 0 Nil
* 0 1 Yellow
* 1 1 Red
* 1 0 Red
*
*/
static void manage_alarm_led(void __iomem *addr, int led_type, int value)
{
static int alarm_major = 0, alarm_minor = 0;
u32 reg = 0x0;
mutex_lock(&alarm_led_lock);
reg = refpga_read(addr, REFPGA_PCIE_ALARM);
(led_type == ALARM_MAJOR_LED) ?
((value == 1) ? (alarm_major = 1) : (alarm_major = 0)) :
((value == 1) ? (alarm_minor = 1) : (alarm_minor = 0));
if (alarm_major) {
reg &= ~BIT(ALARM_MINOR_LED);
reg |= BIT(ALARM_MAJOR_LED);
} else {
if (alarm_minor) {
reg &= ~BIT(ALARM_MAJOR_LED);
reg |= BIT(ALARM_MINOR_LED);
} else {
reg &= ~BIT(ALARM_MINOR_LED);
reg &= ~BIT(ALARM_MAJOR_LED);
}
}
refpga_write(addr, REFPGA_PCIE_ALARM, reg);
mutex_unlock(&alarm_led_lock);
}
static void manage_fan_led(void __iomem *addr, int fan_slot, int value)
{
u8 offset = REFPGA_FAN0_CTRL_STAT + (fan_slot * 2);
u32 reg = 0x0;
reg = refpga_read(addr, offset);
if(value) {
/* Turn on s/w control */
reg = reg | BIT(4);
/* Turn off green led */
reg &= ~BIT(5);
/* Turn on yellow led & make it blink */
reg |= (BIT(6) | BIT(7));
} else {
/* Clear yellow led & stop blink */
reg &= ~(BIT(6) | BIT(7));
/* Stop s/w control */
reg &= ~BIT(4);
}
refpga_write(addr, offset, reg);
}
static void refpga_led_work(struct work_struct *work)
{
struct refpga_led *led = container_of(work, struct refpga_led, work);
void __iomem *addr;
addr = led->addr;
if(strstr(led->lc.name, "fan"))
manage_fan_led(addr, led->bit, led->on);
else
manage_alarm_led(addr, led->bit, led->on);
}
static void refpga_led_brightness_set(struct led_classdev *lc,
enum led_brightness brightness)
{
struct refpga_led *led = container_of(lc, struct refpga_led, lc);
led->on = (brightness != LED_OFF);
led->blink = 0; /* always turn off hw blink on brightness_set() */
schedule_work(&led->work);
}
struct led_table
{
const char *name;
int reg;
};
static struct led_table qfx5200_led_data[] = {
{
.name = "alarm-minor",
.reg = 0,
},
{
.name = "alarm-major",
.reg = 1,
},
{
.name = "fan0-fault",
.reg = 0,
},
{
.name = "fan1-fault",
.reg = 1,
},
{
.name = "fan2-fault",
.reg = 2,
},
{
.name = "fan3-fault",
.reg = 3,
},
{
.name = "fan4-fault",
.reg = 4,
}
};
static int refpga_led_init_one(struct device *dev,
struct refpga_led_data *ild,
int num)
{
struct refpga_led *led;
int ret = 0;
led = &ild->leds[num];
led->addr = tmc_membase;
led->lc.name = qfx5200_led_data[num].name;
led->bit = qfx5200_led_data[num].reg;
led->lc.brightness = LED_OFF;
led->lc.brightness_set = refpga_led_brightness_set;
ret = devm_led_classdev_register(dev, &led->lc);
if (ret) {
dev_err(dev, "devm_led_classdev_register failed\n");
return ret;
}
INIT_WORK(&led->work, refpga_led_work);
return 0;
}
static int refpga_led_qfx5200_init(struct device *dev, struct refpga_led_data *ild)
{
int ret = 0, idx = 0;
if (!dev->parent) {
dev_err(dev, "dev->parent is null\n");
return -ENODEV;
}
ild->num_leds = NUM_LEDS;
ild->leds = devm_kzalloc(dev, sizeof(struct refpga_led) * NUM_LEDS,
GFP_KERNEL);
if (!ild->leds) {
dev_err(dev, "LED allocation failed\n");
return -ENOMEM;
}
for(idx=0; idx<NUM_LEDS; idx++){
ret = refpga_led_init_one(dev, ild, idx);
if (ret)
return ret;
}
return 0;
}
static int jnx_refpga_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct refpga_led_data *ild;
int ret;
ild = devm_kzalloc(dev, sizeof(*ild), GFP_KERNEL);
if (!ild) {
dev_err(dev, "ild allocation failed\n");
return -ENOMEM;
}
ret = refpga_led_qfx5200_init(dev, ild);
if (ret < 0)
return ret;
refpga->led = ild;
return 0;
}
static int jnx_refpga_led_remove(struct platform_device *pdev)
{
struct refpga_chip *drv_data = platform_get_drvdata(pdev);
struct refpga_led_data *ild = drv_data->led;
int i;
for (i = 0; i < ild->num_leds; i++) {
devm_led_classdev_unregister(&pdev->dev, &ild->leds[i].lc);
cancel_work_sync(&ild->leds[i].work);
}
if (ild) {
if (ild->leds)
devm_kfree(&pdev->dev, ild->leds);
devm_kfree(&pdev->dev, ild);
}
return 0;
}
static void reset_fan_full_speed(struct device *dev)
{
u32 val = ~(-1), tmp = ~(-1);
/*
* Reading the REFPGA_PCIE_RESET_CTRL register
*/
val = refpga_read(tmc_membase, REFPGA_PCIE_RESET_CTRL);
/*
* Clearing the fan full_speed bit
*/
val &= ~(REFPGA_RESET_FAN_SPEED);
/*
* Writing the REFPGA_PCIE_RESET_CTRL register
*/
refpga_write(tmc_membase, REFPGA_PCIE_RESET_CTRL, val);
/*
* Reading the REFPGA_PCIE_RESET_CTRL register
*/
tmp = refpga_read(tmc_membase, REFPGA_PCIE_RESET_CTRL);
dev_info(dev, "After resetting fan full speed control: %X\n", tmp);
}
static int jnx_refpga_tmc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
int ret = 0;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "resource allocation failed\n");
return -ENODEV;
}
tmc_membase = devm_ioremap_nocache(dev, res->start, resource_size(res));
if (!tmc_membase) {
dev_err(dev, "ioremap failed\n");
return -ENOMEM;
}
refpga = devm_kzalloc(dev, sizeof(*refpga), GFP_KERNEL);
if (!refpga) {
dev_err(dev, "refpga memory allocation failed\n");
return -ENOMEM;
}
reset_fan_full_speed(dev);
ret = jnx_refpga_led_probe(pdev);
if (ret != 0) {
dev_err(dev, "Refpga LED probe failed\n");
return ret;
}
dev_info(dev, "Refpga LED probe successful: TMC memoy base: %p\n",
tmc_membase);
ret = sysfs_create_group(&dev->kobj, &refpga_fan_attr_group);
if (ret != 0) {
dev_err(dev, "sysfs_create_group failed: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, refpga);
return 0;
}
static int jnx_refpga_tmc_remove(struct platform_device *pdev)
{
jnx_refpga_led_remove(pdev);
sysfs_remove_group(&pdev->dev.kobj, &refpga_fan_attr_group);
return 0;
}
static struct platform_driver jnx_refpga_tmc_driver = {
.driver = {
.name = "refpga-tmc",
.owner = THIS_MODULE,
},
.probe = jnx_refpga_tmc_probe,
.remove = jnx_refpga_tmc_remove,
};
static int __init jnx_refpga_tmc_driver_init(void)
{
int ret = -1;
ret = platform_driver_register(&jnx_refpga_tmc_driver);
return ret;
}
static void __exit jnx_refpga_tmc_driver_exit(void)
{
platform_driver_unregister(&jnx_refpga_tmc_driver);
}
module_init(jnx_refpga_tmc_driver_init);
module_exit(jnx_refpga_tmc_driver_exit);
MODULE_DESCRIPTION("Juniper Networks REFPGA / TMC driver");
MODULE_AUTHOR("Ciju Rajan K <crajank@juniper.net>");
MODULE_LICENSE("GPL");