2019-12-17 17:26:23 -06:00
|
|
|
/*
|
|
|
|
* cls_switchboard.c - PCI device driver for Silverstone Switch board FPGA.
|
|
|
|
*
|
|
|
|
* Author: Pradchaya Phucharoen
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 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/module.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/stddef.h>
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/platform_device.h>
|
2020-02-02 19:01:10 -06:00
|
|
|
#include <linux/platform_data/pca954x.h>
|
2019-12-17 17:26:23 -06:00
|
|
|
#include "cls-i2c-ocore.h"
|
|
|
|
|
|
|
|
#define MOD_VERSION "2.1.0-1"
|
|
|
|
#define DRV_NAME "cls-switchboard"
|
|
|
|
|
|
|
|
#define I2C_MUX_CHANNEL(_ch, _adap_id, _deselect) \
|
|
|
|
[_ch] = { .adap_id = _adap_id, .deselect_on_exit = _deselect }
|
|
|
|
|
|
|
|
#define FPGA_PCIE_DEVICE_ID 0x7021
|
|
|
|
#define MMIO_BAR 0
|
|
|
|
#define I2C_BUS_OFS 9
|
|
|
|
|
|
|
|
/* I2C ocore configurations */
|
|
|
|
#define OCORE_REGSHIFT 2
|
|
|
|
#define OCORE_IP_CLK_khz 62500 //TODO: check fpga's `wb_clk_i`
|
|
|
|
#define OCORE_BUS_CLK_khz 100
|
|
|
|
#define OCORE_REG_IO_WIDTH 1
|
|
|
|
|
|
|
|
/* Optical port xcvr configuration */
|
|
|
|
#define XCVR_REG_SHIFT 2
|
|
|
|
#define XCVR_NUM_PORT 34
|
|
|
|
#define XCVR_PORT_REG_SIZE 0x10
|
|
|
|
|
|
|
|
/* i2c_bus_config - an i2c-core resource and platform data
|
|
|
|
* @id - I2C bus device ID, for identification.
|
|
|
|
* @res - resources for an i2c-core device.
|
|
|
|
* @num_res - size of the resources.
|
|
|
|
* @pdata - a platform data of an i2c-core device.
|
|
|
|
*/
|
|
|
|
struct i2c_bus_config {
|
|
|
|
int id;
|
|
|
|
struct resource *res;
|
|
|
|
ssize_t num_res;
|
|
|
|
struct ocores_i2c_platform_data pdata;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* switchbrd_priv - switchboard private data */
|
|
|
|
struct switchbrd_priv {
|
|
|
|
unsigned long base;
|
|
|
|
int num_i2c_bus;
|
|
|
|
struct platform_device **i2cbuses_pdev;
|
|
|
|
struct platform_device *regio_pdev;
|
|
|
|
struct platform_device *spiflash_pdev;
|
|
|
|
//struct platform_device *xcvr_pdev;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// NOTE: Silverstone i2c channel mapping is very wierd!!!
|
|
|
|
/* PCA9548 channel config on MASTER BUS 3 */
|
|
|
|
static struct pca954x_platform_mode i2c_mux_70_modes[] = {
|
|
|
|
I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 23, true),
|
|
|
|
I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 26, true),
|
|
|
|
I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 27, true),
|
|
|
|
I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 28, true),
|
|
|
|
I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 29, true),
|
|
|
|
I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 30, true),
|
|
|
|
I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 31, true),
|
|
|
|
I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 32, true),
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct pca954x_platform_mode i2c_mux_71_modes[] = {
|
|
|
|
I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 1, true),
|
|
|
|
I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 2, true),
|
|
|
|
I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 3, true),
|
|
|
|
I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 4, true),
|
|
|
|
I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 5, true),
|
|
|
|
I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 6, true),
|
|
|
|
I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 15, true),
|
|
|
|
I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 8, true),
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct pca954x_platform_mode i2c_mux_72_modes[] = {
|
|
|
|
I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 17, true),
|
|
|
|
I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 18, true),
|
|
|
|
I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 19, true),
|
|
|
|
I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 20, true),
|
|
|
|
I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 21, true),
|
|
|
|
I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 22, true),
|
|
|
|
I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 25, true),
|
|
|
|
I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 24, true),
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct pca954x_platform_mode i2c_mux_73_modes[] = {
|
|
|
|
I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 9, true),
|
|
|
|
I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 10, true),
|
|
|
|
I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 11, true),
|
|
|
|
I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 12, true),
|
|
|
|
I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 13, true),
|
|
|
|
I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 14, true),
|
|
|
|
I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 7, true),
|
|
|
|
I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 16, true),
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct pca954x_platform_data om_muxes[] = {
|
|
|
|
{
|
|
|
|
.modes = i2c_mux_70_modes,
|
|
|
|
.num_modes = ARRAY_SIZE(i2c_mux_70_modes),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.modes = i2c_mux_71_modes,
|
|
|
|
.num_modes = ARRAY_SIZE(i2c_mux_71_modes),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.modes = i2c_mux_72_modes,
|
|
|
|
.num_modes = ARRAY_SIZE(i2c_mux_72_modes),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.modes = i2c_mux_73_modes,
|
|
|
|
.num_modes = ARRAY_SIZE(i2c_mux_73_modes),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Optical Module bus 3 i2c muxes info */
|
|
|
|
static struct i2c_board_info i2c_info_3[] = {
|
|
|
|
{
|
|
|
|
I2C_BOARD_INFO("pca9548", 0x70),
|
|
|
|
.platform_data = &om_muxes[0],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
I2C_BOARD_INFO("pca9548", 0x71),
|
|
|
|
.platform_data = &om_muxes[1],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
I2C_BOARD_INFO("pca9548", 0x72),
|
|
|
|
.platform_data = &om_muxes[2],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
I2C_BOARD_INFO("pca9548", 0x73),
|
|
|
|
.platform_data = &om_muxes[3],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* RESOURCE SEPERATES BY FUNCTION */
|
|
|
|
/* Resource IOMEM for i2c bus 1 */
|
|
|
|
static struct resource cls_i2c_res_1[] = {
|
|
|
|
{
|
|
|
|
.start = 0x800, .end = 0x81F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for i2c bus 2 */
|
|
|
|
static struct resource cls_i2c_res_2[] = {
|
|
|
|
{
|
|
|
|
.start = 0x820, .end = 0x83F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for i2c bus 3 */
|
|
|
|
static struct resource cls_i2c_res_3[] = {
|
|
|
|
{
|
|
|
|
.start = 0x840, .end = 0x85F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for i2c bus 4 */
|
|
|
|
static struct resource cls_i2c_res_4[] = {
|
|
|
|
{
|
|
|
|
.start = 0x860, .end = 0x87F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for i2c bus 5 */
|
|
|
|
static struct resource cls_i2c_res_5[] = {
|
|
|
|
{
|
|
|
|
.start = 0x880, .end = 0x89F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for reg access */
|
|
|
|
static struct resource reg_io_res[] = {
|
|
|
|
{
|
|
|
|
.start = 0x00, .end = 0xFF,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for spi flash firmware upgrade */
|
|
|
|
static struct resource spi_flash_res[] = {
|
|
|
|
{
|
|
|
|
.start = 0x1200, .end = 0x121F,
|
|
|
|
.flags = IORESOURCE_MEM,},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Resource IOMEM for front panel XCVR */
|
|
|
|
// static struct resource xcvr_res[] = {
|
|
|
|
// {
|
|
|
|
// .start = 0x4000, .end = 0x421F,
|
|
|
|
// .flags = IORESOURCE_MEM,},
|
|
|
|
// };
|
|
|
|
|
|
|
|
static struct i2c_bus_config i2c_bus_configs[] = {
|
|
|
|
{
|
|
|
|
.id = 1,
|
|
|
|
.res = cls_i2c_res_1,
|
|
|
|
.num_res = ARRAY_SIZE(cls_i2c_res_1),
|
|
|
|
.pdata = {
|
|
|
|
.reg_shift = OCORE_REGSHIFT,
|
|
|
|
.reg_io_width = OCORE_REG_IO_WIDTH,
|
|
|
|
.clock_khz = OCORE_IP_CLK_khz,
|
|
|
|
.bus_khz = OCORE_BUS_CLK_khz,
|
|
|
|
.big_endian = false,
|
|
|
|
.num_devices = 0,
|
|
|
|
.devices = NULL,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.id = 2,
|
|
|
|
.res = cls_i2c_res_2,
|
|
|
|
.num_res = ARRAY_SIZE(cls_i2c_res_2),
|
|
|
|
.pdata = {
|
|
|
|
.reg_shift = OCORE_REGSHIFT,
|
|
|
|
.reg_io_width = OCORE_REG_IO_WIDTH,
|
|
|
|
.clock_khz = OCORE_IP_CLK_khz,
|
|
|
|
.bus_khz = OCORE_BUS_CLK_khz,
|
|
|
|
.big_endian = false,
|
|
|
|
.num_devices = 0,
|
|
|
|
.devices = NULL,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.id = 3,
|
|
|
|
.res = cls_i2c_res_3,
|
|
|
|
.num_res = ARRAY_SIZE(cls_i2c_res_3),
|
|
|
|
.pdata = {
|
|
|
|
.reg_shift = OCORE_REGSHIFT,
|
|
|
|
.reg_io_width = OCORE_REG_IO_WIDTH,
|
|
|
|
.clock_khz = OCORE_IP_CLK_khz,
|
|
|
|
.bus_khz = OCORE_BUS_CLK_khz,
|
|
|
|
.big_endian = false,
|
|
|
|
.num_devices = ARRAY_SIZE(i2c_info_3),
|
|
|
|
.devices = i2c_info_3,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.id = 4,
|
|
|
|
.res = cls_i2c_res_4,
|
|
|
|
.num_res = ARRAY_SIZE(cls_i2c_res_4),
|
|
|
|
.pdata = {
|
|
|
|
.reg_shift = OCORE_REGSHIFT,
|
|
|
|
.reg_io_width = OCORE_REG_IO_WIDTH,
|
|
|
|
.clock_khz = OCORE_IP_CLK_khz,
|
|
|
|
.bus_khz = OCORE_BUS_CLK_khz,
|
|
|
|
.big_endian = false,
|
|
|
|
.num_devices = 0,
|
|
|
|
.devices = NULL,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.id = 5,
|
|
|
|
.res = cls_i2c_res_5,
|
|
|
|
.num_res = ARRAY_SIZE(cls_i2c_res_5),
|
|
|
|
.pdata = {
|
|
|
|
.reg_shift = OCORE_REGSHIFT,
|
|
|
|
.reg_io_width = OCORE_REG_IO_WIDTH,
|
|
|
|
.clock_khz = OCORE_IP_CLK_khz,
|
|
|
|
.bus_khz = OCORE_BUS_CLK_khz,
|
|
|
|
.big_endian = false,
|
|
|
|
.num_devices = 0,
|
|
|
|
.devices = NULL,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: Add a platform configuration struct, and use probe as a factory,
|
|
|
|
// so xcvr, fwupgrade device can configured as options.
|
|
|
|
|
|
|
|
static int cls_fpga_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|
|
|
{
|
|
|
|
|
|
|
|
struct switchbrd_priv *priv;
|
|
|
|
struct platform_device **i2cbuses_pdev;
|
|
|
|
struct platform_device *regio_pdev;
|
|
|
|
struct platform_device *spiflash_pdev;
|
|
|
|
//struct platform_device *xcvr_pdev;
|
|
|
|
unsigned long rstart;
|
|
|
|
int num_i2c_bus, i;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = pci_enable_device(dev);
|
|
|
|
if (err){
|
|
|
|
dev_err(&dev->dev, "Failed to enable PCI device\n");
|
|
|
|
goto err_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check for valid MMIO address */
|
|
|
|
rstart = pci_resource_start(dev, MMIO_BAR);
|
|
|
|
if (!rstart) {
|
|
|
|
dev_err(&dev->dev, "Switchboard base address uninitialized, "
|
|
|
|
"check FPGA\n");
|
|
|
|
err = -ENODEV;
|
|
|
|
goto err_disable_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(&dev->dev, "BAR%d res: 0x%lx-0x%llx\n", MMIO_BAR,
|
|
|
|
rstart, pci_resource_end(dev, MMIO_BAR));
|
|
|
|
|
|
|
|
priv = devm_kzalloc(&dev->dev,
|
|
|
|
sizeof(struct switchbrd_priv), GFP_KERNEL);
|
|
|
|
if (!priv){
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto err_disable_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
pci_set_drvdata(dev, priv);
|
|
|
|
num_i2c_bus = ARRAY_SIZE(i2c_bus_configs);
|
|
|
|
i2cbuses_pdev = devm_kzalloc(
|
|
|
|
&dev->dev,
|
|
|
|
num_i2c_bus * sizeof(struct platform_device*),
|
|
|
|
GFP_KERNEL);
|
|
|
|
|
|
|
|
reg_io_res[0].start += rstart;
|
|
|
|
reg_io_res[0].end += rstart;
|
|
|
|
|
|
|
|
spi_flash_res[0].start += rstart;
|
|
|
|
spi_flash_res[0].end += rstart;
|
|
|
|
|
|
|
|
regio_pdev = platform_device_register_resndata(
|
|
|
|
&dev->dev, "cls-swbrd-io",
|
|
|
|
-1,
|
|
|
|
reg_io_res, ARRAY_SIZE(reg_io_res),
|
|
|
|
NULL, 0);
|
|
|
|
|
|
|
|
if (IS_ERR(regio_pdev)) {
|
|
|
|
dev_err(&dev->dev, "Failed to register cls-swbrd-io\n");
|
|
|
|
err = PTR_ERR(regio_pdev);
|
|
|
|
goto err_disable_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
spiflash_pdev = platform_device_register_resndata(
|
|
|
|
&dev->dev, "cls-swbrd-fwug",
|
|
|
|
-1,
|
|
|
|
spi_flash_res, ARRAY_SIZE(spi_flash_res),
|
|
|
|
NULL, 0);
|
|
|
|
|
|
|
|
if (IS_ERR(spiflash_pdev)) {
|
|
|
|
dev_err(&dev->dev, "Failed to register firmware upgrade node\n");
|
|
|
|
err = PTR_ERR(spiflash_pdev);
|
|
|
|
goto err_unregister_regio;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(i = 0; i < num_i2c_bus; i++){
|
|
|
|
|
|
|
|
i2c_bus_configs[i].res[0].start += rstart;
|
|
|
|
i2c_bus_configs[i].res[0].end += rstart;
|
|
|
|
|
|
|
|
dev_dbg(&dev->dev, "i2c-bus.%d: 0x%llx - 0x%llx\n",
|
|
|
|
i2c_bus_configs[i].id,
|
|
|
|
i2c_bus_configs[i].res[0].start,
|
|
|
|
i2c_bus_configs[i].res[0].end);
|
|
|
|
|
|
|
|
i2cbuses_pdev[i] = platform_device_register_resndata(
|
|
|
|
&dev->dev, "goodcores-i2c",
|
|
|
|
i2c_bus_configs[i].id,
|
|
|
|
i2c_bus_configs[i].res,
|
|
|
|
i2c_bus_configs[i].num_res,
|
|
|
|
&i2c_bus_configs[i].pdata,
|
|
|
|
sizeof(i2c_bus_configs[i].pdata));
|
|
|
|
|
|
|
|
if (IS_ERR(i2cbuses_pdev[i])) {
|
|
|
|
dev_err(&dev->dev, "Failed to register goodcores-i2c.%d\n",
|
|
|
|
i2c_bus_configs[i].id);
|
|
|
|
err = PTR_ERR(i2cbuses_pdev[i]);
|
|
|
|
goto err_unregister_ocore;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->base = rstart;
|
|
|
|
priv->num_i2c_bus = num_i2c_bus;
|
|
|
|
priv->i2cbuses_pdev = i2cbuses_pdev;
|
|
|
|
priv->regio_pdev = regio_pdev;
|
|
|
|
priv->spiflash_pdev = spiflash_pdev;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_unregister_ocore:
|
|
|
|
for(i = 0; i < num_i2c_bus; i++){
|
|
|
|
if(priv->i2cbuses_pdev[i]){
|
|
|
|
platform_device_unregister(priv->i2cbuses_pdev[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// err_unregister_spiflash:
|
|
|
|
// platform_device_unregister(spiflash_pdev);
|
|
|
|
err_unregister_regio:
|
|
|
|
platform_device_unregister(regio_pdev);
|
|
|
|
err_disable_device:
|
|
|
|
pci_disable_device(dev);
|
|
|
|
err_exit:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cls_fpga_remove(struct pci_dev *dev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct switchbrd_priv *priv = pci_get_drvdata(dev);
|
|
|
|
|
|
|
|
for(i = 0; i < priv->num_i2c_bus; i++){
|
|
|
|
if(priv->i2cbuses_pdev[i])
|
|
|
|
platform_device_unregister(priv->i2cbuses_pdev[i]);
|
|
|
|
}
|
|
|
|
platform_device_unregister(priv->spiflash_pdev);
|
|
|
|
platform_device_unregister(priv->regio_pdev);
|
|
|
|
pci_disable_device(dev);
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct pci_device_id pci_clsswbrd[] = {
|
|
|
|
{ PCI_VDEVICE(XILINX, FPGA_PCIE_DEVICE_ID) },
|
|
|
|
{0, }
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(pci, pci_clsswbrd);
|
|
|
|
|
|
|
|
static struct pci_driver clsswbrd_pci_driver = {
|
|
|
|
.name = DRV_NAME,
|
|
|
|
.id_table = pci_clsswbrd,
|
|
|
|
.probe = cls_fpga_probe,
|
|
|
|
.remove = cls_fpga_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
module_pci_driver(clsswbrd_pci_driver);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Pradchaya P.<pphuchar@celestica.com>");
|
|
|
|
MODULE_DESCRIPTION("Celestica Silverstone switchboard driver");
|
|
|
|
MODULE_VERSION(MOD_VERSION);
|
|
|
|
MODULE_LICENSE("GPL");
|