- Support for new platform Questone - Belgite renamed to DS1000 - Hardened bug fixes and unified CLI O/P for Celestica platforms
520 lines
12 KiB
C
520 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* xcvr-cls.c - front panel port control.
|
|
*
|
|
* Pradchaya Phucharoen <pphuchar@celestica.com>
|
|
* Copyright (C) 2019 Celestica Corp.
|
|
*/
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/hwmon.h>
|
|
#include "xcvr-cls.h"
|
|
|
|
/* FPGA front panel */
|
|
#define PORT_CTRL 0
|
|
#define PORT_STATUS 0x4
|
|
#define PORT_INT_STATUS 0x8
|
|
#define PORT_INT_MASK 0xC
|
|
|
|
/*
|
|
* Port control degister
|
|
* LPMOD : active high, RW
|
|
* RST : active low, RW
|
|
* TXDIS : active high, RW
|
|
*/
|
|
#define CTRL_LPMOD BIT(6)
|
|
#define CTRL_RST_L BIT(4)
|
|
#define CTRL_TXDIS BIT(0)
|
|
|
|
/*
|
|
* Port status register
|
|
* IRQ : active low, RO
|
|
* PRESENT : active low, RO, for QSFP
|
|
* TXFAULT : active high, RO
|
|
* RXLOS : active high, RO
|
|
* MODABS : active high, RO, for SFP
|
|
*/
|
|
#define STAT_IRQ_L BIT(5)
|
|
#define STAT_PRESENT_L BIT(4)
|
|
#define STAT_TXFAULT BIT(2)
|
|
#define STAT_RXLOS BIT(1)
|
|
#define STAT_MODABS BIT(0)
|
|
|
|
/*
|
|
* NOTE: Interrupt and mask must be expose as bitfeild.
|
|
* Because the registers of interrupt flags are read-clear.
|
|
*
|
|
* Port interrupt flag resgister
|
|
* INT_N : interrupt flag, set when INT_N is assert.
|
|
* PRESENT : interrupt flag, set when QSFP module plugin/plugout.
|
|
* RXLOS : interrupt flag, set when rxlos is assert.
|
|
* MODABS : interrupt flag, set when SFP module plugin/plugout.
|
|
*/
|
|
#define INTR_INT_N BIT(5)
|
|
#define INTR_PRESENT BIT(4)
|
|
#define INTR_TXFAULT BIT(2)
|
|
#define INTR_RXLOS BIT(1)
|
|
#define INTR_MODABS BIT(0)
|
|
|
|
/*
|
|
* Port interrupt mask register
|
|
* INT_N : active low
|
|
* PRESENT : active low
|
|
* RXLOS_INT : active low
|
|
* MODABS : active low
|
|
*/
|
|
#define MASK_INT_N_L BIT(5)
|
|
#define MASK_PRESENT_L BIT(4)
|
|
#define MASK_TXFAULT_L BIT(2)
|
|
#define MASK_RXLOS_L BIT(1)
|
|
#define MASK_MODABS_L BIT(0)
|
|
|
|
|
|
/*
|
|
* port_data - optical port data
|
|
* @xcvr: xcvr memory accessor
|
|
* @name: port name
|
|
* @index: front panel port index starting from 1
|
|
*/
|
|
struct port_data {
|
|
struct xcvr_priv *xcvr;
|
|
const char *name;
|
|
unsigned int index;
|
|
};
|
|
|
|
/*
|
|
* xcvr_priv - port xcvr private data
|
|
* @dev: device for reference
|
|
* @base: virtual base address
|
|
* @num_ports: number of front panel ports
|
|
* @fp_devs: list of front panel port devices
|
|
*/
|
|
struct xcvr_priv {
|
|
struct device* dev;
|
|
void __iomem *base;
|
|
int port_reg_size;
|
|
int num_ports;
|
|
struct device **fp_devs;
|
|
};
|
|
|
|
static inline void port_setreg(struct xcvr_priv *xcvr, int reg, int index, u8 value)
|
|
{
|
|
return iowrite8(value, xcvr->base + reg + (index - 1) * xcvr->port_reg_size);
|
|
}
|
|
|
|
static inline u8 port_getreg(struct xcvr_priv *xcvr, int reg, int index)
|
|
{
|
|
return ioread8(xcvr->base + reg + (index - 1) * xcvr->port_reg_size);
|
|
}
|
|
|
|
static ssize_t qsfp_modprsL_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_STATUS, index);
|
|
return sprintf(buf, "%d\n", (data & STAT_PRESENT_L)?1:0);
|
|
}
|
|
|
|
static ssize_t qsfp_irqL_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_STATUS, index);
|
|
return sprintf(buf, "%d\n", (data & STAT_IRQ_L)?1:0);
|
|
}
|
|
|
|
static ssize_t qsfp_lpmode_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
return sprintf(buf, "%d\n", (data & CTRL_LPMOD)?1:0);
|
|
}
|
|
|
|
static ssize_t qsfp_lpmode_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
ssize_t status;
|
|
long value;
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
status = kstrtol(buf, 0, &value);
|
|
if (status == 0) {
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
if (value == 0)
|
|
data &= ~CTRL_LPMOD;
|
|
else
|
|
data |= CTRL_LPMOD;
|
|
port_setreg(port_data->xcvr, PORT_CTRL, index, data);
|
|
status = size;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static ssize_t qsfp_resetL_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
return sprintf(buf, "%d\n", (data & CTRL_RST_L)?1:0);
|
|
}
|
|
|
|
static ssize_t qsfp_resetL_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
ssize_t status;
|
|
long value;
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
status = kstrtol(buf, 0, &value);
|
|
if (status == 0) {
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
if (value == 0)
|
|
data &= ~CTRL_RST_L;
|
|
else
|
|
data |= CTRL_RST_L;
|
|
port_setreg(port_data->xcvr, PORT_CTRL, index, data);
|
|
status = size;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static ssize_t sfp_modabs_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_STATUS, index);
|
|
return sprintf(buf, "%d\n", (data & STAT_MODABS)?1:0);
|
|
}
|
|
|
|
static ssize_t sfp_txfault_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_STATUS, index);
|
|
return sprintf(buf, "%d\n", (data & STAT_TXFAULT)?1:0);
|
|
}
|
|
|
|
static ssize_t sfp_rxlos_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_STATUS, index);
|
|
return sprintf(buf, "%d\n", (data & STAT_RXLOS)?1:0);
|
|
}
|
|
|
|
static ssize_t sfp_txdisable_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
return sprintf(buf, "%d\n", (data & CTRL_TXDIS)?1:0);
|
|
}
|
|
|
|
static ssize_t sfp_txdisable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
ssize_t status;
|
|
long value;
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
status = kstrtol(buf, 0, &value);
|
|
if (status == 0) {
|
|
data = port_getreg(port_data->xcvr, PORT_CTRL, index);
|
|
if (value == 0)
|
|
data &= ~CTRL_TXDIS;
|
|
else
|
|
data |= CTRL_TXDIS;
|
|
port_setreg(port_data->xcvr, PORT_CTRL, index, data);
|
|
status = size;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static ssize_t interrupt_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_INT_STATUS, index);
|
|
return sprintf(buf, "0x%2.2x\n", data);
|
|
}
|
|
|
|
static ssize_t interrupt_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
ssize_t status;
|
|
long value;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
status = kstrtoul(buf, 0, &value);
|
|
if (status == 0) {
|
|
port_setreg(port_data->xcvr, PORT_INT_STATUS, index, value);
|
|
status = size;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static ssize_t interrupt_mask_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 data;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
data = port_getreg(port_data->xcvr, PORT_INT_MASK, index);
|
|
return sprintf(buf, "0x%2.2x\n", data);
|
|
}
|
|
|
|
static ssize_t interrupt_mask_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
ssize_t status;
|
|
long value;
|
|
struct port_data *port_data = dev_get_drvdata(dev);
|
|
unsigned int index = port_data->index;
|
|
|
|
status = kstrtoul(buf, 0, &value);
|
|
if (status == 0) {
|
|
port_setreg(port_data->xcvr, PORT_INT_MASK, index, value);
|
|
status = size;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
DEVICE_ATTR_RO(qsfp_modprsL);
|
|
DEVICE_ATTR_RO(qsfp_irqL);
|
|
DEVICE_ATTR_RW(qsfp_lpmode);
|
|
DEVICE_ATTR_RW(qsfp_resetL);
|
|
|
|
DEVICE_ATTR_RO(sfp_modabs);
|
|
DEVICE_ATTR_RO(sfp_txfault);
|
|
DEVICE_ATTR_RO(sfp_rxlos);
|
|
DEVICE_ATTR_RW(sfp_txdisable);
|
|
|
|
DEVICE_ATTR_RW(interrupt);
|
|
DEVICE_ATTR_RW(interrupt_mask);
|
|
|
|
/* qsfp_attrs */
|
|
static struct attribute *qsfp_attrs[] = {
|
|
&dev_attr_qsfp_modprsL.attr,
|
|
&dev_attr_qsfp_lpmode.attr,
|
|
&dev_attr_qsfp_resetL.attr,
|
|
&dev_attr_interrupt.attr,
|
|
&dev_attr_interrupt_mask.attr,
|
|
NULL
|
|
};
|
|
|
|
/* sfp_attrs */
|
|
static struct attribute *sfp_attrs[] = {
|
|
&dev_attr_sfp_modabs.attr,
|
|
&dev_attr_sfp_txfault.attr,
|
|
&dev_attr_sfp_rxlos.attr,
|
|
&dev_attr_sfp_txdisable.attr,
|
|
&dev_attr_interrupt.attr,
|
|
&dev_attr_interrupt_mask.attr,
|
|
NULL
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(qsfp);
|
|
ATTRIBUTE_GROUPS(sfp);
|
|
|
|
/* A single port device init */
|
|
static struct device* init_port(struct device *dev,
|
|
struct xcvr_priv *xcvr,
|
|
struct port_info info,
|
|
const struct attribute_group **groups)
|
|
{
|
|
struct port_data *new_data;
|
|
|
|
new_data = devm_kzalloc(dev, sizeof(struct port_data), GFP_KERNEL);
|
|
if (!new_data)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
new_data->index = info.index;
|
|
new_data->name = info.name;
|
|
new_data->xcvr = xcvr;
|
|
|
|
return devm_hwmon_device_register_with_groups(dev,
|
|
info.name,
|
|
new_data,
|
|
groups);
|
|
}
|
|
|
|
static void xcvr_cleanup(struct xcvr_priv *xcvr)
|
|
{
|
|
struct device *dev;
|
|
struct port_data *data;
|
|
int i;
|
|
|
|
for (i = 0; i < xcvr->num_ports; i++){
|
|
dev = xcvr->fp_devs[i];
|
|
if (dev == NULL)
|
|
continue;
|
|
data = dev_get_drvdata(dev);
|
|
sysfs_remove_link(&xcvr->dev->kobj, data->name);
|
|
}
|
|
}
|
|
|
|
static int cls_xcvr_probe(struct platform_device *pdev)
|
|
{
|
|
|
|
struct xcvr_priv *xcvr;
|
|
struct cls_xcvr_platform_data *pdata;
|
|
struct resource *res;
|
|
int ret;
|
|
int i;
|
|
|
|
struct device **port_devs;
|
|
|
|
xcvr = devm_kzalloc(&pdev->dev, sizeof(struct xcvr_priv), GFP_KERNEL);
|
|
if (!xcvr){
|
|
ret = -ENOMEM;
|
|
goto err_exit;
|
|
}
|
|
|
|
dev_set_drvdata(&pdev->dev, xcvr);
|
|
|
|
/* mmap resource */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (res) {
|
|
xcvr->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(xcvr->base)){
|
|
ret = PTR_ERR(xcvr->base);
|
|
goto err_exit;
|
|
}
|
|
}
|
|
|
|
pdata = dev_get_platdata(&pdev->dev);
|
|
xcvr->dev = &pdev->dev;
|
|
|
|
if (pdata) {
|
|
/* assign pdata */
|
|
xcvr->num_ports = pdata->num_ports;
|
|
xcvr->port_reg_size = pdata->port_reg_size;
|
|
}
|
|
|
|
/* alloc front panel device list */
|
|
port_devs = devm_kzalloc(&pdev->dev,
|
|
xcvr->num_ports * sizeof(struct device*),
|
|
GFP_KERNEL);
|
|
if (!port_devs){
|
|
ret = -ENOMEM;
|
|
goto err_exit;
|
|
}
|
|
|
|
|
|
if (pdata) {
|
|
/* create each device attrs group determined by type */
|
|
for (i = 0; i < pdata->num_ports; i++) {
|
|
struct device *fp_dev;
|
|
|
|
if (pdata->devices[i].type == SFP){
|
|
fp_dev = init_port(&pdev->dev,
|
|
xcvr,
|
|
pdata->devices[i],
|
|
sfp_groups);
|
|
}else{
|
|
fp_dev = init_port(&pdev->dev,
|
|
xcvr,
|
|
pdata->devices[i],
|
|
qsfp_groups);
|
|
}
|
|
if (IS_ERR(fp_dev)) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to init port %s\n",
|
|
pdata->devices[i].name);
|
|
ret = PTR_ERR(fp_dev);
|
|
goto dev_clean_up;
|
|
}
|
|
|
|
dev_info(&pdev->dev,
|
|
"Register port %s\n",
|
|
pdata->devices[i].name);
|
|
|
|
WARN(sysfs_create_link(&pdev->dev.kobj,
|
|
&fp_dev->kobj,
|
|
pdata->devices[i].name),
|
|
"can't create symlink to %s\n", pdata->devices[i].name);
|
|
port_devs[i] = fp_dev;
|
|
fp_dev = NULL;
|
|
}
|
|
xcvr->fp_devs = port_devs;
|
|
}
|
|
|
|
return 0;
|
|
|
|
dev_clean_up:
|
|
xcvr_cleanup(xcvr);
|
|
err_exit:
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
static int cls_xcvr_remove(struct platform_device *pdev)
|
|
{
|
|
struct xcvr_priv *xcvr = dev_get_drvdata(&pdev->dev);
|
|
xcvr_cleanup(xcvr);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct platform_driver cls_xcvr_driver = {
|
|
.probe = cls_xcvr_probe,
|
|
.remove = cls_xcvr_remove,
|
|
.driver = {
|
|
.name = "cls-xcvr",
|
|
},
|
|
};
|
|
|
|
module_platform_driver(cls_xcvr_driver);
|
|
|
|
MODULE_AUTHOR("Pradchaya Phucharoen<pphuchar@celestica.com>");
|
|
MODULE_DESCRIPTION("Celestica xcvr control driver");
|
|
MODULE_VERSION("0.0.1-3");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:cls-xcvr"); |