From 18c92221295aa71bfb6fcc9a22c5d9882eed6523 Mon Sep 17 00:00:00 2001 From: Pradchaya P Date: Tue, 1 Oct 2019 18:55:49 +0700 Subject: [PATCH] [platform/broadcom] Add Celestica Silverstone-DP platform module --- .../installer.conf | 1 + platform/broadcom/one-image.mk | 3 +- platform/broadcom/platform-modules-cel.mk | 8 +- .../sonic-platform-modules-cel/debian/control | 5 + .../platform-modules-silverstone-dp.init | 69 ++ .../platform-modules-silverstone-dp.install | 2 + .../platform-modules-silverstone-dp.postinst | 3 + .../sonic-platform-modules-cel/debian/rules | 2 +- .../cfg/silverstone-dp-modules.conf | 15 + .../silverstone-dp/modules/Makefile | 1 + .../silverstone-dp/modules/baseboard-lpc.c | 433 +++++++++ .../silverstone-dp/modules/cls-switchboard.c | 535 +++++++++++ .../silverstone-dp/modules/i2c-ocores.c | 846 ++++++++++++++++++ .../silverstone-dp/modules/i2c-ocores.h | 22 + .../silverstone-dp/modules/mc24lc64t.c | 174 ++++ .../silverstone-dp/modules/switch_cpld.c | 428 +++++++++ .../silverstone-dp/modules/xcvr-cls.c | 520 +++++++++++ .../silverstone-dp/modules/xcvr-cls.h | 41 + .../platform-modules-silverstone-dp.service | 13 + 19 files changed, 3118 insertions(+), 3 deletions(-) create mode 100644 device/celestica/x86_64-cel_silverstone_dp-r0/installer.conf create mode 100644 platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.init create mode 100644 platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.install create mode 100644 platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.postinst create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/cfg/silverstone-dp-modules.conf create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/Makefile create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/baseboard-lpc.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/cls-switchboard.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.h create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/mc24lc64t.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/switch_cpld.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.c create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.h create mode 100644 platform/broadcom/sonic-platform-modules-cel/silverstone-dp/systemd/platform-modules-silverstone-dp.service diff --git a/device/celestica/x86_64-cel_silverstone_dp-r0/installer.conf b/device/celestica/x86_64-cel_silverstone_dp-r0/installer.conf new file mode 100644 index 000000000000..b8b5b9419485 --- /dev/null +++ b/device/celestica/x86_64-cel_silverstone_dp-r0/installer.conf @@ -0,0 +1 @@ +CONSOLE_SPEED=115200 \ No newline at end of file diff --git a/platform/broadcom/one-image.mk b/platform/broadcom/one-image.mk index 7d50b133ac6f..7592b3f2c120 100644 --- a/platform/broadcom/one-image.mk +++ b/platform/broadcom/one-image.mk @@ -53,7 +53,8 @@ $(SONIC_ONE_IMAGE)_LAZY_INSTALLS += $(DELL_S6000_PLATFORM_MODULE) \ $(ALPHANETWORKS_SNH60B0_640F_PLATFORM_MODULE) \ $(BRCM_XLR_GTS_PLATFORM_MODULE) \ $(DELTA_AG9032V2A_PLATFORM_MODULE) \ - $(JUNIPER_QFX5210_PLATFORM_MODULE) + $(JUNIPER_QFX5210_PLATFORM_MODULE) \ + $(CEL_SILVERSTONE-DP_PLATFORM_MODULE) ifeq ($(INSTALL_DEBUG_TOOLS),y) $(SONIC_ONE_IMAGE)_DOCKERS += $(SONIC_INSTALL_DOCKER_DBG_IMAGES) $(SONIC_ONE_IMAGE)_DOCKERS += $(filter-out $(patsubst %-$(DBG_IMAGE_MARK).gz,%.gz, $(SONIC_INSTALL_DOCKER_DBG_IMAGES)), $(SONIC_INSTALL_DOCKER_IMAGES)) diff --git a/platform/broadcom/platform-modules-cel.mk b/platform/broadcom/platform-modules-cel.mk index 1224faaa6750..aa81fce77ef0 100644 --- a/platform/broadcom/platform-modules-cel.mk +++ b/platform/broadcom/platform-modules-cel.mk @@ -1,10 +1,12 @@ -# Celestica DX010 and Haliburton Platform modules +# Celestica Platform modules CEL_DX010_PLATFORM_MODULE_VERSION = 0.9 CEL_HALIBURTON_PLATFORM_MODULE_VERSION = 0.9 +CEL_SILVERSTONE-DP_PLATFORM_MODULE_VERSION = 0.9 export CEL_DX010_PLATFORM_MODULE_VERSION export CEL_HALIBURTON_PLATFORM_MODULE_VERSION +export CEL_SILVERSTONE-DP_PLATFORM_MODULE_VERSION CEL_DX010_PLATFORM_MODULE = platform-modules-dx010_$(CEL_DX010_PLATFORM_MODULE_VERSION)_amd64.deb $(CEL_DX010_PLATFORM_MODULE)_SRC_PATH = $(PLATFORM_PATH)/sonic-platform-modules-cel @@ -16,4 +18,8 @@ CEL_HALIBURTON_PLATFORM_MODULE = platform-modules-haliburton_$(CEL_HALIBURTON_PL $(CEL_HALIBURTON_PLATFORM_MODULE)_PLATFORM = x86_64-cel_e1031-r0 $(eval $(call add_extra_package,$(CEL_DX010_PLATFORM_MODULE),$(CEL_HALIBURTON_PLATFORM_MODULE))) +CEL_SILVERSTONE-DP_PLATFORM_MODULE = platform-modules-silverstone-dp_$(CEL_SILVERSTONE-DP_PLATFORM_MODULE_VERSION)_amd64.deb +$(CEL_SILVERSTONE-DP_PLATFORM_MODULE)_PLATFORM = x86_64-cel_silverstone_dp-r0 +$(eval $(call add_extra_package,$(CEL_DX010_PLATFORM_MODULE),$(CEL_SILVERSTONE-DP_PLATFORM_MODULE))) + SONIC_STRETCH_DEBS += $(CEL_DX010_PLATFORM_MODULE) diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/control b/platform/broadcom/sonic-platform-modules-cel/debian/control index 445189822039..982854359b39 100644 --- a/platform/broadcom/sonic-platform-modules-cel/debian/control +++ b/platform/broadcom/sonic-platform-modules-cel/debian/control @@ -15,3 +15,8 @@ Package: platform-modules-haliburton Architecture: amd64 Depends: linux-image-4.9.0-9-2-amd64 Description: kernel modules for platform devices such as fan, led, sfp + +Package: platform-modules-silverstone-dp +Architecture: amd64 +Depends: linux-image-4.9.0-9-2-amd64 +Description: kernel modules for platform devices such as led, sfp. diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.init b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.init new file mode 100644 index 000000000000..e1c831c160bf --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.init @@ -0,0 +1,69 @@ +#!/bin/bash + +### BEGIN INIT INFO +# Provides: setup-board +# Required-Start: $portmap +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: S +# Default-Stop: 0 6 +# Short-Description: Setup SilverStone DP board. +### END INIT INFO + + +case "$1" in +start) + echo -n "Setting up board... " + + modprobe i2c-dev + modprobe ipmi_devintf + modprobe mc24lc64t + modprobe baseboard-lpc + modprobe i2c-ocores + modprobe cls-switchboard + modprobe xcvr-cls + modprobe switch_cpld + + # Instantiate TLV EEPROM device on I801 bus + devname=`cat /sys/bus/i2c/devices/i2c-0/name` + if [[ $devname == 'SMBus I801 adapter at '* ]]; then + echo 24lc64t 0x56 > /sys/bus/i2c/devices/i2c-0/new_device + fi + + # Clear syseeprom cache + decode-syseeprom --init 2> /dev/null & + + # Attach switchboard CPLD i2c device + echo switch_cpld 0x30 > /sys/bus/i2c/devices/i2c-4/new_device + + # Attach Optical Module EEPROM + # use optoe1 for QSFP, use optoe3 for QSFP-DD. + for i in {10..33} + do + echo optoe1 0x50 > /sys/bus/i2c/devices/i2c-$i/new_device + done + + for i in {34..39} + do + echo optoe3 0x50 > /sys/bus/i2c/devices/i2c-$i/new_device + done + + echo "done." + ;; + +stop) + echo "done." + ;; + +force-reload|restart) + echo "Not supported" + ;; + +*) + echo "Usage: /etc/init.d/platform-modules-silverstone.init {start|stop}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.install b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.install new file mode 100644 index 000000000000..e9b9ab4f8059 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.install @@ -0,0 +1,2 @@ +silverstone-dp/cfg/silverstone-dp-modules.conf etc/modules-load.d +silverstone-dp/systemd/platform-modules-silverstone-dp.service lib/systemd/system \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.postinst b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.postinst new file mode 100644 index 000000000000..e72b729a03e4 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/debian/platform-modules-silverstone-dp.postinst @@ -0,0 +1,3 @@ +depmod -a +systemctl enable platform-modules-silverstone-dp.service +systemctl start platform-modules-silverstone-dp.service \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/debian/rules b/platform/broadcom/sonic-platform-modules-cel/debian/rules index 6f35290bce26..1825194fb7bf 100755 --- a/platform/broadcom/sonic-platform-modules-cel/debian/rules +++ b/platform/broadcom/sonic-platform-modules-cel/debian/rules @@ -5,7 +5,7 @@ export INSTALL_MOD_DIR:=extra KVERSION ?= $(shell uname -r) KERNEL_SRC := /lib/modules/$(KVERSION) MOD_SRC_DIR:= $(shell pwd) -MODULE_DIRS:= dx010 haliburton +MODULE_DIRS:= dx010 haliburton silverstone-dp %: dh $@ diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/cfg/silverstone-dp-modules.conf b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/cfg/silverstone-dp-modules.conf new file mode 100644 index 000000000000..cb8dcf640ba3 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/cfg/silverstone-dp-modules.conf @@ -0,0 +1,15 @@ +# /etc/modules: kernel modules to load at boot time. +# +# This file contains the names of kernel modules that should be loaded +# at boot time, one per line. Lines beginning with "#" are ignored. + +i2c-i801 +i2c-isch +i2c-ismt +i2c-dev +i2c-mux +i2c-smbus + +i2c-mux-pca954x +ipmi_devintf +ipmi_si \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/Makefile b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/Makefile new file mode 100644 index 000000000000..2702d08e3a31 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/Makefile @@ -0,0 +1 @@ +obj-m := baseboard-lpc.o mc24lc64t.o cls-switchboard.o xcvr-cls.o i2c-ocores.o switch_cpld.o \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/baseboard-lpc.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/baseboard-lpc.c new file mode 100644 index 000000000000..b6291f7d3ce4 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/baseboard-lpc.c @@ -0,0 +1,433 @@ +/* + * baseboard-lpc.c - The CPLD driver for the Base Board of Silverstone + * The driver implement sysfs to access CPLD register on the baseboard of Silverstone via LPC bus. + * Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "baseboard-lpc" +/** + * CPLD register address for read and write. + */ +#define VERSION_ADDR 0xA100 +#define SCRATCH_ADDR 0xA101 +#define BLT_MONTH_ADDR 0xA102 +#define BLT_DATE_ADDR 0xA103 +#define REBOOT_CAUSE 0xA106 +#define SYS_LED_ADDR 0xA162 +#define CPLD_REGISTER_SIZE 0x93 + +/* System reboot cause recorded in CPLD */ +static const struct { + const char *reason; + u8 reset_code; +} reboot_causes[] = { + {"POR", 0x11}, + {"soft-warm-rst", 0x22}, + {"soft-cold-rst", 0x33}, + {"warm-rst", 0x44}, + {"cold-rst", 0x55}, + {"wdt-rst", 0x66}, + {"power-cycle", 0x77} +}; + +struct cpld_b_data { + struct mutex cpld_lock; + uint16_t read_addr; +}; + +struct cpld_b_data *cpld_data; + +static ssize_t scratch_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + unsigned char data = 0; + mutex_lock(&cpld_data->cpld_lock); + data = inb(SCRATCH_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return sprintf(buf,"0x%2.2x\n", data); +} + +static ssize_t scratch_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long data; + char *last; + + mutex_lock(&cpld_data->cpld_lock); + data = (uint16_t)strtoul(buf,&last,16); + if(data == 0 && buf == last){ + mutex_unlock(&cpld_data->cpld_lock); + return -EINVAL; + } + outb(data, SCRATCH_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return count; +} +static DEVICE_ATTR_RW(scratch); + + +/* CPLD version attributes */ +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + u8 version; + mutex_lock(&cpld_data->cpld_lock); + version = inb(VERSION_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return sprintf(buf, "%d.%d\n", version >> 4, version & 0x0F); +} +static DEVICE_ATTR_RO(version); + +/* CPLD version attributes */ +static ssize_t build_date_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + u8 month, day_of_month; + mutex_lock(&cpld_data->cpld_lock); + day_of_month = inb(BLT_DATE_ADDR); + month = inb(BLT_MONTH_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return sprintf(buf, "%x/%x\n", day_of_month, month); +} +static DEVICE_ATTR_RO(build_date); + + +static ssize_t getreg_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + uint16_t addr; + char *last; + + addr = (uint16_t)strtoul(buf,&last,16); + if(addr == 0 && buf == last){ + return -EINVAL; + } + cpld_data->read_addr = addr; + return count; +} + +static ssize_t getreg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int len = 0; + + mutex_lock(&cpld_data->cpld_lock); + len = sprintf(buf, "0x%2.2x\n",inb(cpld_data->read_addr)); + mutex_unlock(&cpld_data->cpld_lock); + return len; +} +static DEVICE_ATTR_RW(getreg); + +static ssize_t setreg_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + uint16_t addr; + uint8_t value; + char *tok; + char clone[count]; + char *pclone = clone; + char *last; + + strcpy(clone, buf); + + mutex_lock(&cpld_data->cpld_lock); + tok = strsep((char**)&pclone, " "); + if(tok == NULL){ + mutex_unlock(&cpld_data->cpld_lock); + return -EINVAL; + } + addr = (uint16_t)strtoul(tok,&last,16); + if(addr == 0 && tok == last){ + mutex_unlock(&cpld_data->cpld_lock); + return -EINVAL; + } + + tok = strsep((char**)&pclone, " "); + if(tok == NULL){ + mutex_unlock(&cpld_data->cpld_lock); + return -EINVAL; + } + value = (uint8_t)strtoul(tok,&last,16); + if(value == 0 && tok == last){ + mutex_unlock(&cpld_data->cpld_lock); + return -EINVAL; + } + + outb(value,addr); + mutex_unlock(&cpld_data->cpld_lock); + return count; +} +static DEVICE_ATTR_WO(setreg); + +/** + * Read all CPLD register in binary mode. + */ +static ssize_t dump_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + unsigned long i=0; + ssize_t status; + + mutex_lock(&cpld_data->cpld_lock); +begin: + if(i < count){ + buf[i++] = inb(VERSION_ADDR + off); + off++; + msleep(1); + goto begin; + } + status = count; + + mutex_unlock(&cpld_data->cpld_lock); + return status; +} +static BIN_ATTR_RO(dump, CPLD_REGISTER_SIZE); + +/** + * Show system led status - on/off/1hz/4hz + * @return Hex string read from scratch register. + */ +static ssize_t sys_led_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + unsigned char data = 0; + mutex_lock(&cpld_data->cpld_lock); + data = inb(SYS_LED_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + data = data & 0x3; + return sprintf(buf, "%s\n", + data == 0x03 ? "off" : data == 0x02 ? "4hz" : data ==0x01 ? "1hz": "on"); +} + +/** + * Set the status of system led - on/off/1hz/4hz + */ +static ssize_t sys_led_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned char led_status,data; + if(sysfs_streq(buf, "off")){ + led_status = 0x03; + }else if(sysfs_streq(buf, "4hz")){ + led_status = 0x02; + }else if(sysfs_streq(buf, "1hz")){ + led_status = 0x01; + }else if(sysfs_streq(buf, "on")){ + led_status = 0x00; + }else{ + count = -EINVAL; + return count; + } + mutex_lock(&cpld_data->cpld_lock); + data = inb(SYS_LED_ADDR); + data = data & ~(0x3); + data = data | led_status; + outb(data, SYS_LED_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return count; +} +static DEVICE_ATTR_RW(sys_led); + +/** + * Show system led color - both/green/yellow/none + * @return Current led color. + */ +static ssize_t sys_led_color_show(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + unsigned char data = 0; + mutex_lock(&cpld_data->cpld_lock); + data = inb(SYS_LED_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + data = (data >> 4) & 0x3; + return sprintf(buf, "%s\n", + data == 0x03 ? "off" : data == 0x02 ? "yellow" : data ==0x01 ? "green": "both"); +} + +/** + * Set the color of system led - both/green/yellow/none + */ +static ssize_t sys_led_color_store(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned char led_status,data; + if(sysfs_streq(buf, "off")){ + led_status = 0x03; + }else if(sysfs_streq(buf, "yellow")){ + led_status = 0x02; + }else if(sysfs_streq(buf, "green")){ + led_status = 0x01; + }else if(sysfs_streq(buf, "both")){ + led_status = 0x00; + }else{ + count = -EINVAL; + return count; + } + mutex_lock(&cpld_data->cpld_lock); + data = inb(SYS_LED_ADDR); + data = data & ~( 0x3 << 4); + data = data | (led_status << 4); + outb(data, SYS_LED_ADDR); + mutex_unlock(&cpld_data->cpld_lock); + return count; +} +static DEVICE_ATTR_RW(sys_led_color); + +static ssize_t reboot_cause_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t status; + u8 reg; + int i; + + mutex_lock(&cpld_data->cpld_lock); + reg = inb(REBOOT_CAUSE); + mutex_unlock(&cpld_data->cpld_lock); + + status = 0; + dev_dbg(dev,"reboot: 0x%x\n", (u8)reg); + for(i = 0; i < ARRAY_SIZE(reboot_causes); i++){ + if((u8)reg == reboot_causes[i].reset_code){ + status = sprintf(buf, "%s\n", + reboot_causes[i].reason); + break; + } + } + return status; +} +DEVICE_ATTR_RO(reboot_cause); + +static struct attribute *cpld_b_attrs[] = { + &dev_attr_version.attr, + &dev_attr_build_date.attr, + &dev_attr_scratch.attr, + &dev_attr_getreg.attr, + &dev_attr_setreg.attr, + &dev_attr_sys_led.attr, + &dev_attr_sys_led_color.attr, + &dev_attr_reboot_cause.attr, + NULL, +}; + +static struct bin_attribute *cpld_b_bin_attrs[] = { + &bin_attr_dump, + NULL, +}; + +static struct attribute_group cpld_b_attrs_grp = { + .attrs = cpld_b_attrs, + .bin_attrs = cpld_b_bin_attrs, +}; + +static struct resource cpld_b_resources[] = { + { + .start = 0xA100, + .end = 0xA192, + .flags = IORESOURCE_IO, + }, +}; + +static void cpld_b_dev_release( struct device * dev) +{ + return; +} + +static struct platform_device cpld_b_dev = { + .name = DRIVER_NAME, + .id = -1, + .num_resources = ARRAY_SIZE(cpld_b_resources), + .resource = cpld_b_resources, + .dev = { + .release = cpld_b_dev_release, + } +}; + +static int cpld_b_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + int err = 0; + + cpld_data = devm_kzalloc(&pdev->dev, sizeof(struct cpld_b_data), + GFP_KERNEL); + if (!cpld_data) + return -ENOMEM; + + mutex_init(&cpld_data->cpld_lock); + + cpld_data->read_addr = VERSION_ADDR; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (unlikely(!res)) { + printk(KERN_ERR "Specified Resource Not Available...\n"); + return -ENODEV; + } + + err = sysfs_create_group(&pdev->dev.kobj, &cpld_b_attrs_grp); + if (err) { + printk(KERN_ERR "Cannot create sysfs for baseboard CPLD\n"); + return err; + } + return 0; +} + +static int cpld_b_drv_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &cpld_b_attrs_grp); + return 0; +} + +static struct platform_driver cpld_b_drv = { + .probe = cpld_b_drv_probe, + .remove = __exit_p(cpld_b_drv_remove), + .driver = { + .name = DRIVER_NAME, + }, +}; + +int cpld_b_init(void) +{ + // Register platform device and platform driver + platform_device_register(&cpld_b_dev); + platform_driver_register(&cpld_b_drv); + return 0; +} + +void cpld_b_exit(void) +{ + // Unregister platform device and platform driver + platform_driver_unregister(&cpld_b_drv); + platform_device_unregister(&cpld_b_dev); +} + +module_init(cpld_b_init); +module_exit(cpld_b_exit); + + +MODULE_AUTHOR("Celestica Inc."); +MODULE_DESCRIPTION("Celestica Silverstone CPLD baseboard driver"); +MODULE_VERSION("0.2.0"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/cls-switchboard.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/cls-switchboard.c new file mode 100644 index 000000000000..ebe5e7742a86 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/cls-switchboard.c @@ -0,0 +1,535 @@ +/* + * cls-switchboard.c - PCI device driver for Silverstone DP 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "i2c-ocores.h" +#include "xcvr-cls.h" + +#define MOD_VERSION "2.1.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 +#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; +}; + +/* I2C bus speed param */ +static int bus_clock_master_1 = 100; +module_param(bus_clock_master_1, int, 0660); +MODULE_PARM_DESC(bus_clock_master_1, + "I2C master 1 bus speed in KHz 50/80/100/200/400"); + +static int bus_clock_master_2 = 100; +module_param(bus_clock_master_2, int, 0660); +MODULE_PARM_DESC(bus_clock_master_2, + "I2C master 2 bus speed in KHz 50/80/100/200/400"); + +static int bus_clock_master_3 = 100; +module_param(bus_clock_master_3, int, 0660); +MODULE_PARM_DESC(bus_clock_master_3, + "I2C master 3 bus speed in KHz 50/80/100/200/400"); + +static int bus_clock_master_4 = 100; +module_param(bus_clock_master_4, int, 0660); +MODULE_PARM_DESC(bus_clock_master_4, + "I2C master 4 bus speed in KHz 50/80/100/200/400"); + +static int bus_clock_master_5 = 100; +module_param(bus_clock_master_5, int, 0660); +MODULE_PARM_DESC(bus_clock_master_5, + "I2C master 5 bus speed in KHz 50/80/100/200/400"); + +/* PCA9548 channel config on MASTER BUS 3 */ +static struct pca954x_platform_mode i2c_mux_70_modes[] = { + I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 1, true), + I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 2, true), + I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 3, true), + I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 4, true), + I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 5, true), + I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 6, true), + I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 7, true), + I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 8, true), +}; + +static struct pca954x_platform_mode i2c_mux_71_modes[] = { + I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 9, true), + I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 10, true), + I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 11, true), + I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 12, true), + I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 13, true), + I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 14, true), + I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 15, true), + I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 16, true), +}; + +static struct pca954x_platform_mode i2c_mux_72_modes[] = { + I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 17, true), + I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 18, true), + I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 19, true), + I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 20, true), + I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 21, true), + I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 22, true), + I2C_MUX_CHANNEL(6, I2C_BUS_OFS + 23, true), + I2C_MUX_CHANNEL(7, I2C_BUS_OFS + 24, true), +}; + +static struct pca954x_platform_mode i2c_mux_73_modes[] = { + I2C_MUX_CHANNEL(0, I2C_BUS_OFS + 25, true), + I2C_MUX_CHANNEL(1, I2C_BUS_OFS + 26, true), + I2C_MUX_CHANNEL(2, I2C_BUS_OFS + 27, true), + I2C_MUX_CHANNEL(3, I2C_BUS_OFS + 28, true), + I2C_MUX_CHANNEL(4, I2C_BUS_OFS + 29, true), + I2C_MUX_CHANNEL(5, I2C_BUS_OFS + 30, 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, + }, + }, +}; + +/* xcvr front panel mapping */ +static struct port_info front_panel_ports[] = { + {"QSFP1", 1, QSFP}, + {"QSFP2", 2, QSFP}, + {"QSFP3", 3, QSFP}, + {"QSFP4", 4, QSFP}, + {"QSFP5", 5, QSFP}, + {"QSFP6", 6, QSFP}, + {"QSFP7", 7, QSFP}, + {"QSFP8", 8, QSFP}, + {"QSFP9", 9, QSFP}, + {"QSFP10", 10, QSFP}, + {"QSFP11", 11, QSFP}, + {"QSFP12", 12, QSFP}, + {"QSFP13", 13, QSFP}, + {"QSFP14", 14, QSFP}, + {"QSFP15", 15, QSFP}, + {"QSFP16", 16, QSFP}, + {"QSFP17", 17, QSFP}, + {"QSFP18", 18, QSFP}, + {"QSFP19", 19, QSFP}, + {"QSFP20", 20, QSFP}, + {"QSFP21", 21, QSFP}, + {"QSFP22", 22, QSFP}, + {"QSFP23", 23, QSFP}, + {"QSFP24", 24, QSFP}, + {"QSFPDD1", 25, QSFP}, + {"QSFPDD2", 26, QSFP}, + {"QSFPDD3", 27, QSFP}, + {"QSFPDD4", 28, QSFP}, + {"QSFPDD5", 29, QSFP}, + {"QSFPDD6", 30, QSFP}, + /* END OF LIST */ +}; + +static struct cls_xcvr_platform_data xcvr_data = { + .port_reg_size = 0x10, + .num_ports = ARRAY_SIZE(front_panel_ports), + .devices = front_panel_ports, +}; + + +// 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 *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; + + xcvr_res[0].start += rstart; + xcvr_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; + } + + xcvr_pdev = platform_device_register_resndata( + NULL, + "cls-xcvr", + -1, + xcvr_res, + ARRAY_SIZE(xcvr_res), + &xcvr_data, + sizeof(xcvr_data)); + + if (IS_ERR(xcvr_pdev)) { + dev_err(&dev->dev, "Failed to register xcvr node\n"); + err = PTR_ERR(xcvr_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; + + switch (i + 1) { + case 1: + i2c_bus_configs[i].pdata.bus_khz = bus_clock_master_1; + break; + case 2: + i2c_bus_configs[i].pdata.bus_khz = bus_clock_master_2; + break; + case 3: + i2c_bus_configs[i].pdata.bus_khz = bus_clock_master_3; + break; + case 4: + i2c_bus_configs[i].pdata.bus_khz = bus_clock_master_4; + break; + case 5: + i2c_bus_configs[i].pdata.bus_khz = bus_clock_master_5; + break; + default: + i2c_bus_configs[i].pdata.bus_khz = OCORE_BUS_CLK_khz; + } + + 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, "cls-ocores-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 ocores-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->xcvr_pdev = xcvr_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_xcvr: + platform_device_unregister(xcvr_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->xcvr_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."); +MODULE_DESCRIPTION("Celestica Silverstone switchboard driver"); +MODULE_VERSION(MOD_VERSION); +MODULE_LICENSE("GPL"); diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.c new file mode 100644 index 000000000000..587744bcb433 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i2c-ocores.c: I2C bus driver for OpenCores I2C controller + * (https://opencores.org/project/i2c/overview) + * + * Peter Korsgaard + * + * Support for the GRLIB port of the controller by + * Andreas Larsson + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "i2c-ocores.h" + +#define OCORES_FLAG_POLL BIT(0) + +/* + * 'process_lock' exists because ocores_process() and ocores_process_timeout() + * can't run in parallel. + */ +struct ocores_i2c { + void __iomem *base; + int iobase; + u32 reg_shift; + u32 reg_io_width; + unsigned long flags; + wait_queue_head_t wait; + struct i2c_adapter adap; + struct i2c_msg *msg; + int pos; + int nmsgs; + int state; /* see STATE_ */ + int nack_retry; + int al_count; + spinlock_t process_lock; + struct clk *clk; + int ip_clock_khz; + int bus_clock_khz; + void (*setreg)(struct ocores_i2c *i2c, int reg, u8 value); + u8 (*getreg)(struct ocores_i2c *i2c, int reg); +}; + +/* registers */ +#define OCI2C_PRELOW 0 +#define OCI2C_PREHIGH 1 +#define OCI2C_CONTROL 2 +#define OCI2C_DATA 3 +#define OCI2C_CMD 4 /* write only */ +#define OCI2C_STATUS 4 /* read only, same address as OCI2C_CMD */ + +#define OCI2C_CTRL_IEN 0x40 +#define OCI2C_CTRL_EN 0x80 + +#define OCI2C_CMD_START 0x91 +#define OCI2C_CMD_STOP 0x41 +#define OCI2C_CMD_READ 0x21 +#define OCI2C_CMD_WRITE 0x11 +#define OCI2C_CMD_READ_ACK 0x21 +#define OCI2C_CMD_READ_NACK 0x29 +#define OCI2C_CMD_IACK 0x01 + +#define OCI2C_STAT_IF 0x01 +#define OCI2C_STAT_TIP 0x02 +#define OCI2C_STAT_ARBLOST 0x20 +#define OCI2C_STAT_BUSY 0x40 +#define OCI2C_STAT_NACK 0x80 + +#define STATE_DONE 0 +#define STATE_START 1 +#define STATE_WRITE 2 +#define STATE_READ 3 +#define STATE_ERROR 4 + +#define TYPE_OCORES 0 +#define TYPE_GRLIB 1 + +#define AL_MAX_ALLOW 10 + +static void oc_setreg_8(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite8(value, i2c->base + (reg << i2c->reg_shift)); +} + +static void oc_setreg_16(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite16(value, i2c->base + (reg << i2c->reg_shift)); +} + +static void oc_setreg_32(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite32(value, i2c->base + (reg << i2c->reg_shift)); +} + +static void oc_setreg_16be(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite16be(value, i2c->base + (reg << i2c->reg_shift)); +} + +static void oc_setreg_32be(struct ocores_i2c *i2c, int reg, u8 value) +{ + iowrite32be(value, i2c->base + (reg << i2c->reg_shift)); +} + +static inline u8 oc_getreg_8(struct ocores_i2c *i2c, int reg) +{ + return ioread8(i2c->base + (reg << i2c->reg_shift)); +} + +static inline u8 oc_getreg_16(struct ocores_i2c *i2c, int reg) +{ + return ioread16(i2c->base + (reg << i2c->reg_shift)); +} + +static inline u8 oc_getreg_32(struct ocores_i2c *i2c, int reg) +{ + return ioread32(i2c->base + (reg << i2c->reg_shift)); +} + +static inline u8 oc_getreg_16be(struct ocores_i2c *i2c, int reg) +{ + return ioread16be(i2c->base + (reg << i2c->reg_shift)); +} + +static inline u8 oc_getreg_32be(struct ocores_i2c *i2c, int reg) +{ + return ioread32be(i2c->base + (reg << i2c->reg_shift)); +} + +static void oc_setreg_io_8(struct ocores_i2c *i2c, int reg, u8 value) +{ + outb(value, i2c->iobase + reg); +} + +static inline u8 oc_getreg_io_8(struct ocores_i2c *i2c, int reg) +{ + return inb(i2c->iobase + reg); +} + +static inline void oc_setreg(struct ocores_i2c *i2c, int reg, u8 value) +{ + i2c->setreg(i2c, reg, value); +} + +static inline u8 oc_getreg(struct ocores_i2c *i2c, int reg) +{ + return i2c->getreg(i2c, reg); +} + +static void ocores_process(struct ocores_i2c *i2c, u8 stat) +{ + struct i2c_msg *msg = i2c->msg; + unsigned long flags; + + /* + * If we spin here is because we are in timeout, so we are going + * to be in STATE_ERROR. See ocores_process_timeout() + */ + spin_lock_irqsave(&i2c->process_lock, flags); + + dev_dbg(&i2c->adap.dev, "STATE: %d\n", i2c->state); + + if ((i2c->state == STATE_DONE) || (i2c->state == STATE_ERROR)) { + /* stop has been sent */ + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); + wake_up(&i2c->wait); + goto out; + } + + /* error? */ + if (stat & OCI2C_STAT_ARBLOST) { + + if (i2c->al_count < AL_MAX_ALLOW){ + i2c->state = STATE_START; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); + dev_dbg(&i2c->adap.dev, "RETRY: AL\n"); + i2c->al_count++; + } else { + i2c->state = STATE_ERROR; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + dev_dbg(&i2c->adap.dev, "ERR: AL\n"); + } + goto out; + } + + if ((i2c->state == STATE_START) || (i2c->state == STATE_WRITE)) { + i2c->state = + (msg->flags & I2C_M_RD) ? STATE_READ : STATE_WRITE; + + if (stat & OCI2C_STAT_NACK) { + dev_dbg(&i2c->adap.dev, "ERR: NACK\n"); + i2c->state = STATE_ERROR; + if (!(msg->flags & I2C_M_RD)) + i2c->nack_retry = 1; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + goto out; + } + } else { + msg->buf[i2c->pos++] = oc_getreg(i2c, OCI2C_DATA); + } + + /* end of msg? */ + if (i2c->pos == msg->len) { + i2c->nmsgs--; + i2c->msg++; + i2c->pos = 0; + msg = i2c->msg; + + if (i2c->nmsgs) { /* end? */ + /* send start? */ + if (!(msg->flags & I2C_M_NOSTART)) { + u8 addr = i2c_8bit_addr_from_msg(msg); + + i2c->state = STATE_START; + + oc_setreg(i2c, OCI2C_DATA, addr); + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); + goto out; + } + i2c->state = (msg->flags & I2C_M_RD) + ? STATE_READ : STATE_WRITE; + } else { + i2c->state = STATE_DONE; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + goto out; + } + } + + if (i2c->state == STATE_READ) { + oc_setreg(i2c, OCI2C_CMD, i2c->pos == (msg->len-1) ? + OCI2C_CMD_READ_NACK : OCI2C_CMD_READ_ACK); + } else { + oc_setreg(i2c, OCI2C_DATA, msg->buf[i2c->pos++]); + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_WRITE); + } + +out: + spin_unlock_irqrestore(&i2c->process_lock, flags); +} + +static irqreturn_t ocores_isr(int irq, void *dev_id) +{ + struct ocores_i2c *i2c = dev_id; + u8 stat = oc_getreg(i2c, OCI2C_STATUS); + + dev_dbg(&i2c->adap.dev, "STATUS: 0x%x\n", stat); + + if (!(stat & OCI2C_STAT_IF)) + return IRQ_NONE; + + ocores_process(i2c, stat); + + return IRQ_HANDLED; +} + +/** + * Process timeout event + * @i2c: ocores I2C device instance + */ +static void ocores_process_timeout(struct ocores_i2c *i2c) +{ + unsigned long flags; + + spin_lock_irqsave(&i2c->process_lock, flags); + i2c->state = STATE_ERROR; + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_STOP); + spin_unlock_irqrestore(&i2c->process_lock, flags); +} + +/** + * Wait until something change in a given register + * @i2c: ocores I2C device instance + * @reg: register to query + * @mask: bitmask to apply on register value + * @val: expected result + * @timeout: timeout in jiffies + * + * Timeout is necessary to avoid to stay here forever when the chip + * does not answer correctly. + * + * Return: 0 on success, -ETIMEDOUT on timeout + */ +static int ocores_wait(struct ocores_i2c *i2c, + int reg, u8 mask, u8 val, + const unsigned long timeout) +{ + unsigned long j; + + j = jiffies + timeout; + while (1) { + u8 status = oc_getreg(i2c, reg); + + if ((status & mask) == val) + break; + + if (time_after(jiffies, j)) + return -ETIMEDOUT; + cpu_relax(); + cond_resched(); + } + return 0; +} + +/** + * Wait until is possible to process some data + * @i2c: ocores I2C device instance + * + * Used when the device is in polling mode (interrupts disabled). + * + * Return: 0 on success, -ETIMEDOUT on timeout + */ +static int ocores_poll_wait(struct ocores_i2c *i2c) +{ + u8 mask; + int err; + + if (i2c->state == STATE_DONE || i2c->state == STATE_ERROR) { + /* transfer is over */ + mask = OCI2C_STAT_BUSY; + } else { + /* on going transfer */ + mask = OCI2C_STAT_TIP; + /* + * We wait for the data to be transferred (8bit), + * then we start polling on the ACK/NACK bit + */ + udelay((8 * 1000) / i2c->bus_clock_khz); + } + + dev_dbg(&i2c->adap.dev, "Wait for: 0x%x\n", mask); + + /* + * once we are here we expect to get the expected result immediately + * so if after 1ms we timeout then something is broken. + */ + err = ocores_wait(i2c, OCI2C_STATUS, mask, 0, msecs_to_jiffies(30)); + if (err) + dev_warn(i2c->adap.dev.parent, + "%s: STATUS timeout, bit 0x%x did not clear in 30ms\n", + __func__, mask); + return err; +} + +/** + * It handles an IRQ-less transfer + * @i2c: ocores I2C device instance + * + * Even if IRQ are disabled, the I2C OpenCore IP behavior is exactly the same + * (only that IRQ are not produced). This means that we can re-use entirely + * ocores_isr(), we just add our polling code around it. + * + * It can run in atomic context + */ +static void ocores_process_polling(struct ocores_i2c *i2c) +{ + while (1) { + irqreturn_t ret; + int err; + + err = ocores_poll_wait(i2c); + if (err) { + i2c->state = STATE_ERROR; + break; /* timeout */ + } + + ret = ocores_isr(-1, i2c); + if (ret == IRQ_NONE) + break; /* all messages have been transferred */ + } +} + +static int ocores_xfer_core(struct ocores_i2c *i2c, + struct i2c_msg *msgs, int num, + bool polling) +{ + int ret; + u8 ctrl; + + ctrl = oc_getreg(i2c, OCI2C_CONTROL); + if (polling) + oc_setreg(i2c, OCI2C_CONTROL, ctrl & ~OCI2C_CTRL_IEN); + else + oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_IEN); + + i2c->msg = msgs; + i2c->pos = 0; + i2c->nmsgs = num; + i2c->state = STATE_START; + i2c->al_count = 0; + + dev_dbg(&i2c->adap.dev, "STATE: %d\n", i2c->state); + + oc_setreg(i2c, OCI2C_DATA, i2c_8bit_addr_from_msg(i2c->msg)); + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_START); + + if (polling) { + ocores_process_polling(i2c); + } else { + ret = wait_event_timeout(i2c->wait, + (i2c->state == STATE_ERROR) || + (i2c->state == STATE_DONE), HZ); + if (ret == 0) { + ocores_process_timeout(i2c); + return -ETIMEDOUT; + } + } + + return (i2c->state == STATE_DONE) ? num : -EIO; +} + +static int ocores_xfer_polling(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + return ocores_xfer_core(i2c_get_adapdata(adap), msgs, num, true); +} + +static int ocores_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + int ret; + int retry = 0; + int max_retry = 0; + struct ocores_i2c *i2c = i2c_get_adapdata(adap); + + i2c->nack_retry = 0; + + if (i2c->flags & OCORES_FLAG_POLL) { + ret = ocores_xfer_polling(adap, msgs, num); + if (num == 1) + max_retry = 5; + else + max_retry = 5; + + while ((i2c->nack_retry == 1) && (retry < max_retry)) + { + retry++; + i2c->nack_retry = 0; + ret = ocores_xfer_polling(adap, msgs, num); + } + i2c->nack_retry = 0; + return ret; + } + return ocores_xfer_core(i2c, msgs, num, false); +} + +static int ocores_init(struct device *dev, struct ocores_i2c *i2c) +{ + int prescale; + int diff; + u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); + + /* make sure the device is disabled */ + ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); + oc_setreg(i2c, OCI2C_CONTROL, ctrl); + + prescale = (i2c->ip_clock_khz / (5 * i2c->bus_clock_khz)) - 1; + prescale = clamp(prescale, 0, 0xffff); + + diff = i2c->ip_clock_khz / (5 * (prescale + 1)) - i2c->bus_clock_khz; + if (abs(diff) > i2c->bus_clock_khz / 10) { + dev_err(dev, + "Unsupported clock settings: core: %d KHz, bus: %d KHz\n", + i2c->ip_clock_khz, i2c->bus_clock_khz); + return -EINVAL; + } + + oc_setreg(i2c, OCI2C_PRELOW, prescale & 0xff); + oc_setreg(i2c, OCI2C_PREHIGH, prescale >> 8); + + dev_info(dev, "Set bus speed to %d KHz\n", i2c->bus_clock_khz); + dev_info(dev, "Prescale: %d\n", prescale); + + /* Init the device */ + oc_setreg(i2c, OCI2C_CMD, OCI2C_CMD_IACK); + oc_setreg(i2c, OCI2C_CONTROL, ctrl | OCI2C_CTRL_EN); + + return 0; +} + + +static u32 ocores_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm ocores_algorithm = { + .master_xfer = ocores_xfer, + .functionality = ocores_func, +}; + +static const struct i2c_adapter ocores_adapter = { + .owner = THIS_MODULE, + .name = "cls-i2c-ocores", + .class = I2C_CLASS_DEPRECATED, + .algo = &ocores_algorithm, +}; + +static const struct of_device_id ocores_i2c_match[] = { + { + .compatible = "opencores,i2c-ocores", + .data = (void *)TYPE_OCORES, + }, + { + .compatible = "aeroflexgaisler,i2cmst", + .data = (void *)TYPE_GRLIB, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ocores_i2c_match); + +#ifdef CONFIG_OF +/* + * Read and write functions for the GRLIB port of the controller. Registers are + * 32-bit big endian and the PRELOW and PREHIGH registers are merged into one + * register. The subsequent registers have their offsets decreased accordingly. + */ +static u8 oc_getreg_grlib(struct ocores_i2c *i2c, int reg) +{ + u32 rd; + int rreg = reg; + + if (reg != OCI2C_PRELOW) + rreg--; + rd = ioread32be(i2c->base + (rreg << i2c->reg_shift)); + if (reg == OCI2C_PREHIGH) + return (u8)(rd >> 8); + else + return (u8)rd; +} + +static void oc_setreg_grlib(struct ocores_i2c *i2c, int reg, u8 value) +{ + u32 curr, wr; + int rreg = reg; + + if (reg != OCI2C_PRELOW) + rreg--; + if (reg == OCI2C_PRELOW || reg == OCI2C_PREHIGH) { + curr = ioread32be(i2c->base + (rreg << i2c->reg_shift)); + if (reg == OCI2C_PRELOW) + wr = (curr & 0xff00) | value; + else + wr = (((u32)value) << 8) | (curr & 0xff); + } else { + wr = value; + } + iowrite32be(wr, i2c->base + (rreg << i2c->reg_shift)); +} + +static int ocores_i2c_of_probe(struct platform_device *pdev, + struct ocores_i2c *i2c) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + u32 val; + u32 clock_frequency; + bool clock_frequency_present; + + if (of_property_read_u32(np, "reg-shift", &i2c->reg_shift)) { + /* no 'reg-shift', check for deprecated 'regstep' */ + if (!of_property_read_u32(np, "regstep", &val)) { + if (!is_power_of_2(val)) { + dev_err(&pdev->dev, "invalid regstep %d\n", + val); + return -EINVAL; + } + i2c->reg_shift = ilog2(val); + dev_warn(&pdev->dev, + "regstep property deprecated, use reg-shift\n"); + } + } + + clock_frequency_present = !of_property_read_u32(np, "clock-frequency", + &clock_frequency); + i2c->bus_clock_khz = 100; + + i2c->clk = devm_clk_get(&pdev->dev, NULL); + + if (!IS_ERR(i2c->clk)) { + int ret = clk_prepare_enable(i2c->clk); + + if (ret) { + dev_err(&pdev->dev, + "clk_prepare_enable failed: %d\n", ret); + return ret; + } + i2c->ip_clock_khz = clk_get_rate(i2c->clk) / 1000; + if (clock_frequency_present) + i2c->bus_clock_khz = clock_frequency / 1000; + } + + if (i2c->ip_clock_khz == 0) { + if (of_property_read_u32(np, "opencores,ip-clock-frequency", + &val)) { + if (!clock_frequency_present) { + dev_err(&pdev->dev, + "Missing required parameter 'opencores,ip-clock-frequency'\n"); + clk_disable_unprepare(i2c->clk); + return -ENODEV; + } + i2c->ip_clock_khz = clock_frequency / 1000; + dev_warn(&pdev->dev, + "Deprecated usage of the 'clock-frequency' property, please update to 'opencores,ip-clock-frequency'\n"); + } else { + i2c->ip_clock_khz = val / 1000; + if (clock_frequency_present) + i2c->bus_clock_khz = clock_frequency / 1000; + } + } + + of_property_read_u32(pdev->dev.of_node, "reg-io-width", + &i2c->reg_io_width); + + match = of_match_node(ocores_i2c_match, pdev->dev.of_node); + if (match && (long)match->data == TYPE_GRLIB) { + dev_dbg(&pdev->dev, "GRLIB variant of i2c-ocores\n"); + i2c->setreg = oc_setreg_grlib; + i2c->getreg = oc_getreg_grlib; + } + + return 0; +} +#else +#define ocores_i2c_of_probe(pdev, i2c) -ENODEV +#endif + +static int ocores_i2c_probe(struct platform_device *pdev) +{ + struct ocores_i2c *i2c; + struct ocores_i2c_platform_data *pdata; + struct resource *res; + int irq; + int ret; + int i; + + i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + spin_lock_init(&i2c->process_lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) { + i2c->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(i2c->base)) + return PTR_ERR(i2c->base); + } else { + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -EINVAL; + i2c->iobase = res->start; + if (!devm_request_region(&pdev->dev, res->start, + resource_size(res), + pdev->name)) { + dev_err(&pdev->dev, "Can't get I/O resource.\n"); + return -EBUSY; + } + i2c->setreg = oc_setreg_io_8; + i2c->getreg = oc_getreg_io_8; + } + + pdata = dev_get_platdata(&pdev->dev); + if (pdata) { + i2c->reg_shift = pdata->reg_shift; + i2c->reg_io_width = pdata->reg_io_width; + i2c->ip_clock_khz = pdata->clock_khz; + if (pdata->bus_khz) + i2c->bus_clock_khz = pdata->bus_khz; + else + i2c->bus_clock_khz = 100; + } else { + ret = ocores_i2c_of_probe(pdev, i2c); + if (ret) + return ret; + } + + if (i2c->reg_io_width == 0) + i2c->reg_io_width = 1; /* Set to default value */ + + if (!i2c->setreg || !i2c->getreg) { + bool be = pdata ? pdata->big_endian : + of_device_is_big_endian(pdev->dev.of_node); + + switch (i2c->reg_io_width) { + case 1: + i2c->setreg = oc_setreg_8; + i2c->getreg = oc_getreg_8; + break; + + case 2: + i2c->setreg = be ? oc_setreg_16be : oc_setreg_16; + i2c->getreg = be ? oc_getreg_16be : oc_getreg_16; + break; + + case 4: + i2c->setreg = be ? oc_setreg_32be : oc_setreg_32; + i2c->getreg = be ? oc_getreg_32be : oc_getreg_32; + break; + + default: + dev_err(&pdev->dev, "Unsupported I/O width (%d)\n", + i2c->reg_io_width); + ret = -EINVAL; + goto err_clk; + } + } + + init_waitqueue_head(&i2c->wait); + + irq = platform_get_irq(pdev, 0); + if (irq == -ENXIO) { + i2c->flags |= OCORES_FLAG_POLL; + } else { + if (irq < 0) + return irq; + } + + if (!(i2c->flags & OCORES_FLAG_POLL)) { + ret = devm_request_irq(&pdev->dev, irq, ocores_isr, 0, + pdev->name, i2c); + if (ret) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + goto err_clk; + } + } + + ret = ocores_init(&pdev->dev, i2c); + if (ret) + goto err_clk; + + /* hook up driver to tree */ + platform_set_drvdata(pdev, i2c); + i2c->adap = ocores_adapter; + i2c_set_adapdata(&i2c->adap, i2c); + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.dev.of_node = pdev->dev.of_node; + + /* add i2c adapter to i2c tree */ + ret = i2c_add_adapter(&i2c->adap); + if (ret) + goto err_clk; + + /* add in known devices to the bus */ + if (pdata) { + for (i = 0; i < pdata->num_devices; i++) + i2c_new_device(&i2c->adap, pdata->devices + i); + } + + return 0; + +err_clk: + clk_disable_unprepare(i2c->clk); + return ret; +} + +static int ocores_i2c_remove(struct platform_device *pdev) +{ + struct ocores_i2c *i2c = platform_get_drvdata(pdev); + u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); + + /* disable i2c logic */ + ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); + oc_setreg(i2c, OCI2C_CONTROL, ctrl); + + /* remove adapter & data */ + i2c_del_adapter(&i2c->adap); + + if (!IS_ERR(i2c->clk)) + clk_disable_unprepare(i2c->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int ocores_i2c_suspend(struct device *dev) +{ + struct ocores_i2c *i2c = dev_get_drvdata(dev); + u8 ctrl = oc_getreg(i2c, OCI2C_CONTROL); + + /* make sure the device is disabled */ + ctrl &= ~(OCI2C_CTRL_EN | OCI2C_CTRL_IEN); + oc_setreg(i2c, OCI2C_CONTROL, ctrl); + + if (!IS_ERR(i2c->clk)) + clk_disable_unprepare(i2c->clk); + return 0; +} + +static int ocores_i2c_resume(struct device *dev) +{ + struct ocores_i2c *i2c = dev_get_drvdata(dev); + + if (!IS_ERR(i2c->clk)) { + unsigned long rate; + int ret = clk_prepare_enable(i2c->clk); + + if (ret) { + dev_err(dev, + "clk_prepare_enable failed: %d\n", ret); + return ret; + } + rate = clk_get_rate(i2c->clk) / 1000; + if (rate) + i2c->ip_clock_khz = rate; + } + return ocores_init(dev, i2c); +} + +static SIMPLE_DEV_PM_OPS(ocores_i2c_pm, ocores_i2c_suspend, ocores_i2c_resume); +#define OCORES_I2C_PM (&ocores_i2c_pm) +#else +#define OCORES_I2C_PM NULL +#endif + +static struct platform_driver ocores_i2c_driver = { + .probe = ocores_i2c_probe, + .remove = ocores_i2c_remove, + .driver = { + .name = "cls-ocores-i2c", + .of_match_table = ocores_i2c_match, + .pm = OCORES_I2C_PM, + }, +}; + +module_platform_driver(ocores_i2c_driver); + +MODULE_AUTHOR("Peter Korsgaard "); +MODULE_AUTHOR("Pradchaya P "); +MODULE_AUTHOR("Saranpong C "); +MODULE_DESCRIPTION("OpenCores I2C bus driver"); +MODULE_VERSION("1.0.1"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cls-ocores-i2c"); diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.h b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.h new file mode 100644 index 000000000000..25926b385255 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/i2c-ocores.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i2c-ocores.h - definitions for the i2c-ocores interface + * + * Peter Korsgaard + */ + +#ifndef _LINUX_I2C_OCORES_H +#define _LINUX_I2C_OCORES_H + +struct ocores_i2c_platform_data { + u32 reg_shift; /* register offset shift value */ + u32 reg_io_width; /* register io read/write width */ + u32 clock_khz; /* input clock in kHz */ + u32 bus_khz; /* bus clock in kHz */ + bool big_endian; /* registers are big endian */ + u8 num_devices; /* number of devices in the devices list */ + struct i2c_board_info const *devices; /* devices connected to the bus */ +}; + +#endif /* _LINUX_I2C_OCORES_H */ + diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/mc24lc64t.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/mc24lc64t.c new file mode 100644 index 000000000000..fc15bb74b68d --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/mc24lc64t.c @@ -0,0 +1,174 @@ +/* + * mc24lc64t.c - driver for Microchip 24LC64T + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define EEPROM_SIZE 8192 //mc24lt64t eeprom size in bytes. + +struct mc24lc64t_data { + struct mutex update_lock; +}; + +static ssize_t mc24lc64t_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct mc24lc64t_data *drvdata = i2c_get_clientdata(client); + unsigned long timeout, read_time, i = 0; + int status; + + mutex_lock(&drvdata->update_lock); + + if (i2c_smbus_write_byte_data(client, off>>8, off)) + { + status = -EIO; + goto exit; + } + + msleep(1); + +begin: + + if (i < count) + { + timeout = jiffies + msecs_to_jiffies(25); /* 25 mS timeout*/ + do { + read_time = jiffies; + + status = i2c_smbus_read_byte(client); + if (status >= 0) + { + buf[i++] = status; + goto begin; + } + } while (time_before(read_time, timeout)); + + status = -ETIMEDOUT; + goto exit; + } + + status = count; + +exit: + mutex_unlock(&drvdata->update_lock); + + return status; +} + + +static ssize_t mc24lc64t_write (struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count){ + + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct mc24lc64t_data *drvdata = i2c_get_clientdata(client); + unsigned long timeout, write_time, i = 0; + int status; + u16 value; + + mutex_lock(&drvdata->update_lock); + +begin: + if (i < count){ + timeout = jiffies + msecs_to_jiffies(25); /* 25 mS timeout*/ + value = (buf[i] << 8 | ( off &0xff)); + do { + write_time = jiffies; + status = i2c_smbus_write_word_data(client, off>>8, value); + if (status >= 0) + { + // increase offset + off++; + // increase buffer index + i++; + goto begin; + } + } while (time_before(write_time, timeout)); + status = -ETIMEDOUT; + goto exit; + } + status = count; + +exit: + mutex_unlock(&drvdata->update_lock); + return status; +} + + +static struct bin_attribute mc24lc64t_bit_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO | S_IWUGO, + }, + .size = EEPROM_SIZE, + .read = mc24lc64t_read, + .write = mc24lc64t_write, +}; + +static int mc24lc64t_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct mc24lc64t_data *drvdata; + int err; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA + | I2C_FUNC_SMBUS_READ_BYTE)) + return -EPFNOSUPPORT; + + if (!(drvdata = devm_kzalloc(&client->dev, + sizeof(struct mc24lc64t_data), GFP_KERNEL))) + return -ENOMEM; + + i2c_set_clientdata(client, drvdata); + mutex_init(&drvdata->update_lock); + + err = sysfs_create_bin_file(&client->dev.kobj, &mc24lc64t_bit_attr); + return err; +} + +static int mc24lc64t_remove(struct i2c_client *client) +{ + struct mc24lc64t_data *drvdata = i2c_get_clientdata(client); + sysfs_remove_bin_file(&client->dev.kobj, &mc24lc64t_bit_attr); + + return 0; +} + +static const struct i2c_device_id mc24lc64t_id[] = { + { "24lc64t", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mc24lc64t_id); + +static struct i2c_driver mc24lc64t_driver = { + .driver = { + .name = "mc24lc64t", + .owner = THIS_MODULE, + }, + .probe = mc24lc64t_probe, + .remove = mc24lc64t_remove, + .id_table = mc24lc64t_id, +}; + +module_i2c_driver(mc24lc64t_driver); + +MODULE_AUTHOR("Abhisit Sangjan "); +MODULE_DESCRIPTION("Microchip 24LC64T Driver"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/switch_cpld.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/switch_cpld.c new file mode 100644 index 000000000000..72fedf1f5eb1 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/switch_cpld.c @@ -0,0 +1,428 @@ +/* + * switch_cpld.c - i2c driver for Silverstone DP switchboard CPLD1/CPLD2 + * provides sysfs interfaces to access CPLD register and control port LEDs + * + * Author: Budsakol Sirirattanasakul + * + * 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 +#include +#include +#include +#include +#include + +#define CPLD1_ADDR 0x30 +#define CPLD2_ADDR 0x31 + +#define SCRATCH_ADDR 0x01 +#define LED_OPMODE 0x09 +#define LED_TEST 0x0A + +struct switch_cpld_data { + struct mutex lock; + struct i2c_client *client; + struct i2c_client *client2; + uint8_t read_addr; +}; + +static ssize_t getreg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int value; + + value = i2c_smbus_read_byte_data(client, data->read_addr); + if (value < 0) + return value; + + return sprintf(buf, "0x%.2x\n", value); +} + +static ssize_t getreg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + uint8_t value; + ssize_t status; + struct switch_cpld_data *data = dev_get_drvdata(dev); + + status = kstrtou8(buf, 0, &value); + if (status != 0) + return status; + + data->read_addr = value; + + return size; +} + +static ssize_t setreg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + uint8_t addr, value; + ssize_t status; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + char *tok; + + tok = strsep((char **)&buf, " "); + if (tok == NULL) + return -EINVAL; + status = kstrtou8(tok, 0, &addr); + if (status != 0) + return status; + + tok = strsep((char **)&buf, " "); + if (tok == NULL) + return -EINVAL; + status = kstrtou8(tok, 0, &value); + if (status != 0) + return status; + + status = i2c_smbus_write_byte_data(client, addr, value); + if (status == 0) + status = size; + return status; +} + +static ssize_t scratch_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int value; + + value = i2c_smbus_read_byte_data(client, SCRATCH_ADDR); + if (value < 0) + return value; + + return sprintf(buf, "0x%.2x\n", value); +} + +static ssize_t scratch_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + uint8_t value; + ssize_t status; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + status = kstrtou8(buf, 0, &value); + if (status != 0) + return status; + status = i2c_smbus_write_byte_data(client, SCRATCH_ADDR, value); + if (status == 0) + status = size; + return status; +} + +DEVICE_ATTR_RW(getreg); +DEVICE_ATTR_WO(setreg); +DEVICE_ATTR_RW(scratch); + +static struct attribute *switch_cpld_attrs[] = { + &dev_attr_getreg.attr, + &dev_attr_setreg.attr, + &dev_attr_scratch.attr, + NULL, +}; +ATTRIBUTE_GROUPS(switch_cpld); + +static ssize_t port_led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int led_mode_1, led_mode_2; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client1 = data->client; + struct i2c_client *client2 = data->client2; + + led_mode_1 = i2c_smbus_read_byte_data(client1, LED_OPMODE); + if (led_mode_1 < 0) + return led_mode_1; + + led_mode_2 = i2c_smbus_read_byte_data(client2, LED_OPMODE); + if (led_mode_2 < 0) + return led_mode_2; + + return sprintf(buf, "%s %s\n", + led_mode_1 ? "test" : "normal", + led_mode_2 ? "test" : "normal"); +} + +static ssize_t port_led_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int status; + uint8_t led_mode; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client1 = data->client; + struct i2c_client *client2 = data->client2; + + if (sysfs_streq(buf, "test")) + led_mode = 0x01; + else if (sysfs_streq(buf, "normal")) + led_mode = 0x00; + else + return -EINVAL; + + status = i2c_smbus_write_byte_data(client1, LED_OPMODE, led_mode); + if (status != 0) { + return status; + } + + status = i2c_smbus_write_byte_data(client2, LED_OPMODE, led_mode); + if (status != 0) { + return status; + } + + return size; +} + +static ssize_t port_led_color_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int led_color1, led_color2; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client1 = data->client; + struct i2c_client *client2 = data->client2; + + led_color1 = i2c_smbus_read_byte_data(client1, LED_TEST); + if (led_color1 < 0) + return led_color1; + + led_color2 = i2c_smbus_read_byte_data(client2, LED_TEST); + if (led_color2 < 0) + return led_color2; + + return sprintf(buf, "%s %s\n", + led_color1 == 0x02 ? "green" : + led_color1 == 0x01 ? "amber" : "off", + + led_color2 == 0x07 ? "off" : + led_color2 == 0x06 ? "green" : + led_color2 == 0x05 ? "red" : + led_color2 == 0x04 ? "yellow" : + led_color2 == 0x03 ? "blue" : + led_color2 == 0x02 ? "cyan" : + led_color2 == 0x01 ? "magenta" : "white"); +} + +static ssize_t port_led_color_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int status; + uint8_t led_color1, led_color2; + struct switch_cpld_data *data = dev_get_drvdata(dev); + struct i2c_client *client1 = data->client; + struct i2c_client *client2 = data->client2; + + if (sysfs_streq(buf, "off")) { + led_color1 = 0x07; + led_color2 = 0x07; + } else if (sysfs_streq(buf, "green")) { + led_color1 = 0x07; + led_color2 = 0x06; + } else if (sysfs_streq(buf, "red")) { + led_color1 = 0x07; + led_color2 = 0x05; + } else if (sysfs_streq(buf, "yellow")) { + led_color1 = 0x07; + led_color2 = 0x04; + } else if (sysfs_streq(buf, "blue")) { + led_color1 = 0x07; + led_color2 = 0x03; + } else if (sysfs_streq(buf, "cyan")) { + led_color1 = 0x02; + led_color2 = 0x02; + } else if (sysfs_streq(buf, "magenta")) { + led_color1 = 0x01; + led_color2 = 0x01; + } else if (sysfs_streq(buf, "white")) { + led_color1 = 0x07; + led_color2 = 0x00; + } else { + return -EINVAL; + } + + status = i2c_smbus_write_byte_data(client1, LED_TEST, led_color1); + if (status != 0) { + return status; + } + + status = i2c_smbus_write_byte_data(client2, LED_TEST, led_color2); + if (status != 0) { + return status; + } + + return size; +} + +DEVICE_ATTR_RW(port_led_mode); +DEVICE_ATTR_RW(port_led_color); + +static struct attribute *sff_led_attrs[] = { + &dev_attr_port_led_mode.attr, + &dev_attr_port_led_color.attr, + NULL, +}; + +static struct attribute_group sff_led_groups = { + .attrs = sff_led_attrs, +}; + +static int switch_cpld_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + struct switch_cpld_data *drvdata1, *drvdata2; + struct device *hwmon_dev1, *hwmon_dev2; + struct i2c_client *client2; + + if (client->addr != CPLD1_ADDR) { + dev_err(&client->dev, "probe, bad i2c addr: 0x%x\n", + client->addr); + err = -EINVAL; + goto exit; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EPFNOSUPPORT; + + /* CPLD1 */ + drvdata1 = devm_kzalloc(&client->dev, + sizeof(struct switch_cpld_data), GFP_KERNEL); + + if (!drvdata1) { + err = -ENOMEM; + goto exit; + } + + mutex_init(&drvdata1->lock); + drvdata1->client = client; + drvdata1->read_addr = 0x00; + i2c_set_clientdata(client, drvdata1); + hwmon_dev1 = devm_hwmon_device_register_with_groups(&client->dev, + "CPLD1", + drvdata1, + switch_cpld_groups); + + if (IS_ERR(hwmon_dev1)) { + err = PTR_ERR(hwmon_dev1); + goto exit; + } + + err = sysfs_create_link(&client->dev.kobj, &hwmon_dev1->kobj, "CPLD1"); + if (err) { + goto exit; + } + + /* CPLD2 */ + drvdata2 = devm_kzalloc(&client->dev, + sizeof(struct switch_cpld_data), GFP_KERNEL); + + if (!drvdata2) { + err = -ENOMEM; + goto err_link; + } + + client2 = i2c_new_dummy(client->adapter, CPLD2_ADDR); + if (!client2) { + dev_err(&client->dev, "address 0x%02x unavailable\n", + CPLD2_ADDR); + err = -EADDRINUSE; + goto err_link; + } + + mutex_init(&drvdata2->lock); + drvdata2->read_addr = 0x00; + drvdata2->client = client2; + i2c_set_clientdata(client2, drvdata2); + + /* attach client2 to be client2 of CPLD1 + for later use on port led sysfs */ + drvdata1->client2 = client2; + + hwmon_dev2 = devm_hwmon_device_register_with_groups(&client2->dev, + "CPLD2", + drvdata2, + switch_cpld_groups); + + if (IS_ERR(hwmon_dev2)) { + err = PTR_ERR(hwmon_dev2); + goto err_client2; + } + + err = sysfs_create_link(&client->dev.kobj, &hwmon_dev2->kobj, "CPLD2"); + if (err) { + goto err_client2; + } + + //port led + err = sysfs_create_group(&client->dev.kobj, &sff_led_groups); + if (err) { + dev_err(&client->dev, + "failed to create sysfs attribute group.\n"); + goto err_link2; + } + + return 0; + +err_link2: + sysfs_remove_link(&client->dev.kobj, "CPLD2"); + +err_client2: + if (client2) + i2c_unregister_device(client2); + +err_link: + sysfs_remove_link(&client->dev.kobj, "CPLD1"); + +exit: + dev_err(&client->dev, "probe error %d\n", err); + return err; +} + +static int switch_cpld_remove(struct i2c_client *client) +{ + struct switch_cpld_data *data = i2c_get_clientdata(client); + + sysfs_remove_group(&client->dev.kobj, &sff_led_groups); + sysfs_remove_link(&data->client->dev.kobj, "CPLD2"); + sysfs_remove_link(&client->dev.kobj, "CPLD1"); + i2c_unregister_device(data->client2); + return 0; +} + +static const struct i2c_device_id switch_cpld_ids[] = { + { "switch_cpld", 0x30 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, switch_cpld_ids); + +static struct i2c_driver switch_cpld_driver = { + .driver = { + .name = "switch_cpld", + .owner = THIS_MODULE, + }, + .probe = switch_cpld_probe, + .remove = switch_cpld_remove, + .id_table = switch_cpld_ids, +}; + +module_i2c_driver(switch_cpld_driver); + +MODULE_AUTHOR("Budsakol Sirirattanasakul"); +MODULE_DESCRIPTION("Celestica Silverstone Switchboard CPLD driver"); +MODULE_VERSION("1.0.0"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.c b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.c new file mode 100644 index 000000000000..bc0da613ec8d --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * xcvr-cls.c - front panel port control. + * + * Pradchaya Phucharoen + * Copyright (C) 2019 Celestica Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#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"); +MODULE_DESCRIPTION("Celestica xcvr control driver"); +MODULE_VERSION("0.0.1-3"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cls-xcvr"); \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.h b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.h new file mode 100644 index 000000000000..7659a7c0e9cc --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/modules/xcvr-cls.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * xcvr-cls.h + * + * Pradchaya Phucharoen + * Copyright (C) 2019 Celestica Corp. + */ + +#ifndef _LINUX_I2C_CLS_H +#define _LINUX_I2C_CLS_H + +enum PORT_TYPE { + NONE = 0, + SFP, + QSFP +}; + +/* + * port_info - optical port info + * @index: front panel port index starting from 1 + * @typr: port type, see *PORT_TYPE* + */ +struct port_info { + const char *name; + unsigned int index; + enum PORT_TYPE type; +}; + +/* + * cls_xcvr_platform_data - port xcvr private data + * @port_reg_size: register range of each port + * @num_ports: number of front panel ports + * @devices: list of front panel port info + */ +struct cls_xcvr_platform_data { + unsigned int port_reg_size; + int num_ports; + struct port_info *devices; +}; + +#endif /* _LINUX_I2C_CLS_H */ \ No newline at end of file diff --git a/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/systemd/platform-modules-silverstone-dp.service b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/systemd/platform-modules-silverstone-dp.service new file mode 100644 index 000000000000..21d7426d1c27 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-cel/silverstone-dp/systemd/platform-modules-silverstone-dp.service @@ -0,0 +1,13 @@ +[Unit] +Description=Celestica Silverstone DP platform modules +After=local-fs.target +Before=pmon.service + +[Service] +Type=oneshot +ExecStart=-/etc/init.d/platform-modules-silverstone-dp start +ExecStop=-/etc/init.d/platform-modules-silverstone-dp stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target \ No newline at end of file