507 lines
14 KiB
C
507 lines
14 KiB
C
/*
|
|
* dx010_cpld.c - driver for SeaStone's CPLD
|
|
*
|
|
* Copyright (C) 2017 Celestica Corp.
|
|
*
|
|
* 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/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/io.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/err.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/types.h>
|
|
#include <uapi/linux/stat.h>
|
|
|
|
#define DRIVER_NAME "dx010_cpld"
|
|
|
|
#define LPMOD0108 0x252
|
|
#define LPMOD0910 0x253
|
|
#define LPMOD1118 0x2d2
|
|
#define LPMOD1921 0x2d3
|
|
#define LPMOD2229 0x3d2
|
|
#define LPMOD3032 0x3d3
|
|
|
|
#define ABS0108 0x254
|
|
#define ABS0910 0x255
|
|
#define ABS1118 0x2d4
|
|
#define ABS1921 0x2d5
|
|
#define ABS2229 0x3d4
|
|
#define ABS3032 0x3d5
|
|
|
|
#define INT0108 0x256
|
|
#define INT0910 0x257
|
|
#define INT1118 0x2d6
|
|
#define INT1921 0x2d7
|
|
#define INT2229 0x3d6
|
|
#define INT3032 0x3d7
|
|
|
|
#define LENGTH_PORT_CPLD 34
|
|
#define PORT_BANK1_START 1
|
|
#define PORT_BANK1_END 10
|
|
#define PORT_BANK2_START 11
|
|
#define PORT_BANK2_END 21
|
|
#define PORT_BANK3_START 22
|
|
#define PORT_BANK3_END 32
|
|
#define PORT_SFPP1 33
|
|
#define PORT_SFPP2 34
|
|
|
|
#define PORT_ID_BANK1 0x210
|
|
#define PORT_ID_BANK2 0x290
|
|
#define PORT_ID_BANK3 0x390
|
|
|
|
#define OPCODE_ID_BANK1 0x211
|
|
#define OPCODE_ID_BANK2 0x291
|
|
#define OPCODE_ID_BANK3 0x391
|
|
|
|
#define DEVADDR_ID_BANK1 0x212
|
|
#define DEVADDR_ID_BANK2 0x292
|
|
#define DEVADDR_ID_BANK3 0x392
|
|
|
|
#define CMDBYT_ID_BANK1 0x213
|
|
#define CMDBYT_ID_BANK2 0x293
|
|
#define CMDBYT_ID_BANK3 0x393
|
|
|
|
#define WRITE_ID_BANK1 0x220
|
|
#define WRITE_ID_BANK2 0x2A0
|
|
#define WRITE_ID_BANK3 0x3A0
|
|
|
|
#define READ_ID_BANK1 0x230
|
|
#define READ_ID_BANK2 0x2B0
|
|
#define READ_ID_BANK3 0x3B0
|
|
|
|
#define SSRR_ID_BANK1 0x216
|
|
#define SSRR_ID_BANK2 0x296
|
|
#define SSRR_ID_BANK3 0x396
|
|
|
|
#define HST_CNTL2_QUICK 0x00
|
|
#define HST_CNTL2_BYTE 0x01
|
|
#define HST_CNTL2_BYTE_DATA 0x02
|
|
#define HST_CNTL2_WORD_DATA 0x03
|
|
#define HST_CNTL2_BLOCK 0x05
|
|
|
|
struct dx010_i2c_data {
|
|
int portid;
|
|
};
|
|
|
|
struct dx010_cpld_data {
|
|
struct i2c_adapter *i2c_adapter[LENGTH_PORT_CPLD];
|
|
struct mutex cpld_lock;
|
|
};
|
|
|
|
struct dx010_cpld_data *cpld_data;
|
|
|
|
static ssize_t get_lpmode(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
unsigned long lpmod = 0;
|
|
|
|
mutex_lock(&cpld_data->cpld_lock);
|
|
|
|
lpmod =
|
|
(inb(LPMOD3032) & 0x07) << (24+5) |
|
|
inb(LPMOD2229) << (24-3) |
|
|
(inb(LPMOD1921) & 0x07) << (16 + 2) |
|
|
inb(LPMOD1118) << (16-6) |
|
|
(inb(LPMOD0910) & 0x03 ) << 8 |
|
|
inb(LPMOD0108);
|
|
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
|
|
return sprintf(buf,"0x%8.8lx\n", lpmod & 0xffffffff);
|
|
}
|
|
|
|
static ssize_t set_lpmode(struct device *dev, struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned long lpmod;
|
|
int err;
|
|
|
|
mutex_lock(&cpld_data->cpld_lock);
|
|
|
|
err = kstrtoul(buf, 16, &lpmod);
|
|
if (err)
|
|
{
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
return err;
|
|
}
|
|
|
|
outb( (lpmod >> 0) & 0xFF, LPMOD0108);
|
|
outb( (lpmod >> 8) & 0x03, LPMOD0910);
|
|
outb( (lpmod >> 10) & 0xFF, LPMOD1118);
|
|
outb( (lpmod >> 18) & 0x07, LPMOD1921);
|
|
outb( (lpmod >> 21) & 0xFF, LPMOD2229);
|
|
outb( (lpmod >> 29) & 0x07, LPMOD3032);
|
|
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t get_modprs(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
unsigned long present;
|
|
|
|
mutex_lock(&cpld_data->cpld_lock);
|
|
|
|
present =
|
|
(inb(ABS3032) & 0x07) << (24+5) |
|
|
inb(ABS2229) << (24-3) |
|
|
(inb(ABS1921) & 0x07) << (16 + 2) |
|
|
inb(ABS1118) << (16-6) |
|
|
(inb(ABS0910) & 0x03) << 8 |
|
|
inb(ABS0108);
|
|
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
|
|
return sprintf(buf,"0x%8.8lx\n", present & 0xffffffff);
|
|
}
|
|
|
|
static ssize_t get_modirq(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
unsigned long irq;
|
|
|
|
mutex_lock(&cpld_data->cpld_lock);
|
|
|
|
irq =
|
|
(inb(INT3032) & 0x07) << (24+5) |
|
|
inb(INT2229) << (24-3) |
|
|
(inb(INT1921) & 0x07) << (16 + 2) |
|
|
inb(INT1118) << (16-6) |
|
|
(inb(INT0910) & 0x03) << 8 |
|
|
inb(INT0108);
|
|
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
|
|
return sprintf(buf,"0x%8.8lx\n", irq & 0xffffffff);
|
|
}
|
|
|
|
static DEVICE_ATTR(qsfp_lpmode, S_IRUGO | S_IWUSR, get_lpmode, set_lpmode);
|
|
static DEVICE_ATTR(qsfp_modprs, S_IRUGO, get_modprs, NULL);
|
|
static DEVICE_ATTR(qsfp_modirq, S_IRUGO, get_modirq, NULL);
|
|
|
|
static struct attribute *dx010_lpc_attrs[] = {
|
|
&dev_attr_qsfp_lpmode.attr,
|
|
&dev_attr_qsfp_modprs.attr,
|
|
&dev_attr_qsfp_modirq.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group dx010_lpc_attr_grp = {
|
|
.attrs = dx010_lpc_attrs,
|
|
};
|
|
|
|
static struct resource cel_dx010_lpc_resources[] = {
|
|
{
|
|
.flags = IORESOURCE_IO,
|
|
},
|
|
};
|
|
|
|
static void cel_dx010_lpc_dev_release( struct device * dev)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static struct platform_device cel_dx010_lpc_dev = {
|
|
.name = DRIVER_NAME,
|
|
.id = -1,
|
|
.num_resources = ARRAY_SIZE(cel_dx010_lpc_resources),
|
|
.resource = cel_dx010_lpc_resources,
|
|
.dev = {
|
|
.release = cel_dx010_lpc_dev_release,
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Read eeprom of QSFP device.
|
|
* @param a i2c adapter.
|
|
* @param addr address to read.
|
|
* @param new_data QSFP port number struct.
|
|
* @param cmd i2c command.
|
|
* @return 0 if not error, else the error code.
|
|
*/
|
|
static int i2c_read_eeprom(struct i2c_adapter *a, u16 addr,
|
|
struct dx010_i2c_data *new_data, u8 cmd, union i2c_smbus_data *data){
|
|
|
|
u32 reg;
|
|
int ioBase=0;
|
|
char byte;
|
|
short temp;
|
|
short portid, opcode, devaddr, cmdbyte0, ssrr, writedata, readdata;
|
|
__u16 word_data;
|
|
char read_byte;
|
|
int error = -EIO;
|
|
|
|
mutex_lock(&cpld_data->cpld_lock);
|
|
|
|
if (((new_data->portid >= PORT_BANK1_START)
|
|
&& (new_data->portid <= PORT_BANK1_END))
|
|
|| (new_data->portid == PORT_SFPP1)
|
|
|| (new_data->portid == PORT_SFPP2))
|
|
{
|
|
portid = PORT_ID_BANK1;
|
|
opcode = OPCODE_ID_BANK1;
|
|
devaddr = DEVADDR_ID_BANK1;
|
|
cmdbyte0 = CMDBYT_ID_BANK1;
|
|
ssrr = SSRR_ID_BANK1;
|
|
writedata = WRITE_ID_BANK1;
|
|
readdata = READ_ID_BANK1;
|
|
}else if ((new_data->portid >= PORT_BANK2_START) && (new_data->portid <= PORT_BANK2_END)){
|
|
portid = PORT_ID_BANK2;
|
|
opcode = OPCODE_ID_BANK2;
|
|
devaddr = DEVADDR_ID_BANK2;
|
|
cmdbyte0 = CMDBYT_ID_BANK2;
|
|
ssrr = SSRR_ID_BANK2;
|
|
writedata = WRITE_ID_BANK2;
|
|
readdata = READ_ID_BANK2;
|
|
}else if ((new_data->portid >= PORT_BANK3_START) && (new_data->portid <= PORT_BANK3_END)){
|
|
portid = PORT_ID_BANK3;
|
|
opcode = OPCODE_ID_BANK3;
|
|
devaddr = DEVADDR_ID_BANK3;
|
|
cmdbyte0 = CMDBYT_ID_BANK3;
|
|
ssrr = SSRR_ID_BANK3;
|
|
writedata = WRITE_ID_BANK3;
|
|
readdata = READ_ID_BANK3;
|
|
}else{
|
|
/* Invalid parameter! */
|
|
error = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
while ((inb(ioBase + ssrr) & 0x40));
|
|
if ((inb(ioBase + ssrr) & 0x80) == 0x80) {
|
|
error = -EIO;
|
|
/* Read error reset the port */
|
|
outb(0x00, ioBase + ssrr);
|
|
udelay(3000);
|
|
outb(0x01, ioBase + ssrr);
|
|
goto exit;
|
|
}
|
|
|
|
byte = 0x40 +new_data->portid;
|
|
reg = cmd;
|
|
outb(byte, ioBase + portid);
|
|
outb(reg,ioBase + cmdbyte0);
|
|
byte = 33;
|
|
outb(byte, ioBase + opcode);
|
|
addr = addr << 1;
|
|
addr |= 0x01;
|
|
outb(addr, ioBase + devaddr);
|
|
while ((inb(ioBase + ssrr) & 0x40))
|
|
{
|
|
udelay(100);
|
|
}
|
|
|
|
if ((inb(ioBase + ssrr) & 0x80) == 0x80) {
|
|
/* Read error reset the port */
|
|
error = -EIO;
|
|
outb(0x00, ioBase + ssrr);
|
|
udelay(3000);
|
|
outb(0x01, ioBase + ssrr);
|
|
goto exit;
|
|
}
|
|
|
|
temp = ioBase + readdata;
|
|
word_data = inb(temp);
|
|
word_data |= (inb(++temp) << 8);
|
|
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
data->word = word_data;
|
|
return 0;
|
|
|
|
exit:
|
|
mutex_unlock(&cpld_data->cpld_lock);
|
|
return error;
|
|
}
|
|
|
|
static int dx010_i2c_access(struct i2c_adapter *a, u16 addr,
|
|
unsigned short flags, char rw, u8 cmd,
|
|
int size, union i2c_smbus_data *data)
|
|
{
|
|
|
|
int error = 0;
|
|
|
|
struct dx010_i2c_data *new_data;
|
|
|
|
/* Write the command register */
|
|
new_data = i2c_get_adapdata(a);
|
|
|
|
/* Map the size to what the chip understands */
|
|
switch (size) {
|
|
case I2C_SMBUS_QUICK:
|
|
size = HST_CNTL2_QUICK;
|
|
break;
|
|
case I2C_SMBUS_BYTE:
|
|
size = HST_CNTL2_BYTE;
|
|
break;
|
|
case I2C_SMBUS_BYTE_DATA:
|
|
size = HST_CNTL2_BYTE_DATA;
|
|
break;
|
|
case I2C_SMBUS_WORD_DATA:
|
|
size = HST_CNTL2_WORD_DATA;
|
|
break;
|
|
case I2C_SMBUS_BLOCK_DATA:
|
|
size = HST_CNTL2_BLOCK;
|
|
break;
|
|
default:
|
|
dev_warn(&a->dev, "Unsupported transaction %d\n", size);
|
|
error = -EOPNOTSUPP;
|
|
goto Done;
|
|
}
|
|
|
|
switch (size) {
|
|
case HST_CNTL2_BYTE: /* Result put in SMBHSTDAT0 */
|
|
break;
|
|
case HST_CNTL2_BYTE_DATA:
|
|
break;
|
|
case HST_CNTL2_WORD_DATA:
|
|
if( 0 == i2c_read_eeprom(a,addr,new_data,cmd,data)){
|
|
error = 0;
|
|
}else{
|
|
error = -EIO;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Done:
|
|
return error;
|
|
}
|
|
|
|
static u32 dx010_i2c_func(struct i2c_adapter *a)
|
|
{
|
|
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
|
|
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
|
|
I2C_FUNC_SMBUS_BLOCK_DATA;
|
|
}
|
|
|
|
static const struct i2c_algorithm dx010_i2c_algorithm = {
|
|
.smbus_xfer = dx010_i2c_access,
|
|
.functionality = dx010_i2c_func,
|
|
};
|
|
|
|
static struct i2c_adapter * cel_dx010_i2c_init(struct platform_device *pdev, int portid)
|
|
{
|
|
int error;
|
|
|
|
struct i2c_adapter *new_adapter;
|
|
struct dx010_i2c_data *new_data;
|
|
|
|
new_adapter = kzalloc(sizeof(*new_adapter), GFP_KERNEL);
|
|
if (!new_adapter)
|
|
return NULL;
|
|
|
|
new_adapter->dev.parent = &pdev->dev;
|
|
new_adapter->owner = THIS_MODULE;
|
|
new_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
|
|
new_adapter->algo = &dx010_i2c_algorithm;
|
|
|
|
snprintf(new_adapter->name, sizeof(new_adapter->name),
|
|
"SMBus dx010 i2c Adapter portid@%04x", portid);
|
|
|
|
new_data = kzalloc(sizeof(*new_data), GFP_KERNEL);
|
|
if (!new_data)
|
|
return NULL;
|
|
|
|
new_data->portid = portid;
|
|
|
|
i2c_set_adapdata(new_adapter,new_data);
|
|
|
|
error = i2c_add_adapter(new_adapter);
|
|
if(error)
|
|
return NULL;
|
|
|
|
return new_adapter;
|
|
};
|
|
|
|
static int cel_dx010_lpc_drv_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
int ret =0;
|
|
int portid_count;
|
|
|
|
cpld_data = devm_kzalloc(&pdev->dev, sizeof(struct dx010_cpld_data),
|
|
GFP_KERNEL);
|
|
if (!cpld_data)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&cpld_data->cpld_lock);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
|
if (unlikely(!res)) {
|
|
printk(KERN_ERR " Specified Resource Not Available...\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = sysfs_create_group(&pdev->dev.kobj, &dx010_lpc_attr_grp);
|
|
if (ret) {
|
|
printk(KERN_ERR "Cannot create sysfs\n");
|
|
}
|
|
|
|
for(portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++)
|
|
cpld_data->i2c_adapter[portid_count-1] =
|
|
cel_dx010_i2c_init(pdev, portid_count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cel_dx010_lpc_drv_remove(struct platform_device *pdev)
|
|
{
|
|
int portid_count;
|
|
struct dx010_i2c_data *new_data;
|
|
|
|
sysfs_remove_group(&pdev->dev.kobj, &dx010_lpc_attr_grp);
|
|
|
|
for (portid_count=1 ; portid_count<=LENGTH_PORT_CPLD ; portid_count++)
|
|
i2c_del_adapter(cpld_data->i2c_adapter[portid_count-1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver cel_dx010_lpc_drv = {
|
|
.probe = cel_dx010_lpc_drv_probe,
|
|
.remove = __exit_p(cel_dx010_lpc_drv_remove),
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
},
|
|
};
|
|
|
|
int cel_dx010_lpc_init(void)
|
|
{
|
|
platform_device_register(&cel_dx010_lpc_dev);
|
|
platform_driver_register(&cel_dx010_lpc_drv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cel_dx010_lpc_exit(void)
|
|
{
|
|
platform_driver_unregister(&cel_dx010_lpc_drv);
|
|
platform_device_unregister(&cel_dx010_lpc_dev);
|
|
}
|
|
|
|
module_init(cel_dx010_lpc_init);
|
|
module_exit(cel_dx010_lpc_exit);
|
|
|
|
MODULE_AUTHOR("Abhisit Sangjan <asang@celestica.com>");
|
|
MODULE_AUTHOR("Pariwat Leamsumran <pleamsum@celestica.com>");
|
|
MODULE_DESCRIPTION("Celestica SeaStone DX010 LPC Driver");
|
|
MODULE_LICENSE("GPL");
|