From 649a8bb36b6306baf032bc1693efd036823f297e Mon Sep 17 00:00:00 2001 From: Steve Noble Date: Wed, 23 Feb 2022 15:21:33 +0000 Subject: [PATCH] Revert "[as4224/as5114/as4564] Upgrade kernel to 5.15" This reverts commit c3e7db2e7886de672424ec28e30f8b11383be0fb. --- .../5.15-lts/configs/arm64-all/Makefile | 2 +- .../arm64/as4224/as4224-52p/modules/PKG.yml | 2 +- .../as4224/as4224-52p/modules/builds/Makefile | 4 +- .../r0/src/lib/arm64-accton-as4224-52p-r0.yml | 4 +- .../arm64/as4224/as4224-52t/modules/PKG.yml | 2 +- .../as4224/as4224-52t/modules/builds/Makefile | 4 +- .../r0/src/lib/arm64-accton-as4224-52t-r0.yml | 4 +- .../arm64/as4224/as5114-48x/modules/PKG.yml | 2 +- .../as4224/as5114-48x/modules/builds/Makefile | 4 +- .../r0/src/lib/arm64-accton-as5114-48x-r0.yml | 4 +- .../src/{modules => modules-5_10}/.gitignore | 0 .../src/{modules => modules-5_10}/Makefile | 0 .../arm64-accton-as4224-cpld.c | 0 .../arm64-accton-as4224-fan.c | 0 .../arm64-accton-as4224-gpio-i2c.c | 0 .../arm64-accton-as4224-psu.c | 0 .../src/{modules => modules-5_10}/optoe.c | 0 .../arm64/as4224/src/modules-5_6/.gitignore | 6 + .../arm64/as4224/src/modules-5_6/Makefile | 5 + .../modules-5_6/arm64-accton-as4224-cpld.c | 1518 +++++++++++++++++ .../src/modules-5_6/arm64-accton-as4224-fan.c | 601 +++++++ .../arm64-accton-as4224-gpio-i2c.c | 245 +++ .../src/modules-5_6/arm64-accton-as4224-psu.c | 327 ++++ .../arm64/as4224/src/modules-5_6/optoe.c | 1148 +++++++++++++ .../accton/arm64/as4564-26p/modules/PKG.yml | 2 +- .../arm64/as4564-26p/modules/builds/Makefile | 2 +- .../r0/src/lib/arm64-accton-as4564-26p-r0.yml | 4 +- 27 files changed, 3870 insertions(+), 20 deletions(-) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/.gitignore (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/Makefile (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/arm64-accton-as4224-cpld.c (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/arm64-accton-as4224-fan.c (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/arm64-accton-as4224-gpio-i2c.c (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/arm64-accton-as4224-psu.c (100%) rename packages/platforms/accton/arm64/as4224/src/{modules => modules-5_10}/optoe.c (100%) create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/.gitignore create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/Makefile create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-cpld.c create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-fan.c create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-gpio-i2c.c create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-psu.c create mode 100644 packages/platforms/accton/arm64/as4224/src/modules-5_6/optoe.c diff --git a/packages/base/any/kernels/5.15-lts/configs/arm64-all/Makefile b/packages/base/any/kernels/5.15-lts/configs/arm64-all/Makefile index 5e50fac24..fd96792a6 100644 --- a/packages/base/any/kernels/5.15-lts/configs/arm64-all/Makefile +++ b/packages/base/any/kernels/5.15-lts/configs/arm64-all/Makefile @@ -15,7 +15,7 @@ K_PATCH_SERIES=series.arm64 include ../../kconfig.mk K_CONFIG := arm64-all.config -K_BUILD_TARGET := Image Image.gz freescale/fsl-ls1043a-rdb.dtb freescale/fsl-ls1046a-rdb-sdk.dtb freescale/fsl-ls2080a-rdb.dtb freescale/fsl-ls2088a-rdb.dtb freescale/fsl-ls1088a-rdb.dtb marvell/accton-as4224.dtb marvell/accton-as5114.dtb marvell/accton-as4564-26p.dtb +K_BUILD_TARGET := Image Image.gz freescale/fsl-ls1043a-rdb.dtb freescale/fsl-ls1046a-rdb-sdk.dtb freescale/fsl-ls2080a-rdb.dtb freescale/fsl-ls2088a-rdb.dtb freescale/fsl-ls1088a-rdb.dtb K_COPY_SRC := arch/arm64/boot/Image K_COPY_GZIP := 1 ifndef K_COPY_DST diff --git a/packages/platforms/accton/arm64/as4224/as4224-52p/modules/PKG.yml b/packages/platforms/accton/arm64/as4224/as4224-52p/modules/PKG.yml index 247a46a33..a2296af30 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52p/modules/PKG.yml +++ b/packages/platforms/accton/arm64/as4224/as4224-52p/modules/PKG.yml @@ -1 +1 @@ -!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4224-52p KERNELS="onl-kernel-5.15-lts-arm64-all:arm64" +!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4224-52p KERNELS="onl-kernel-5.10-lts-arm64-all:arm64" diff --git a/packages/platforms/accton/arm64/as4224/as4224-52p/modules/builds/Makefile b/packages/platforms/accton/arm64/as4224/as4224-52p/modules/builds/Makefile index b0de05ba9..d73213f60 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52p/modules/builds/Makefile +++ b/packages/platforms/accton/arm64/as4224/as4224-52p/modules/builds/Makefile @@ -1,5 +1,5 @@ -KERNELS := onl-kernel-5.15-lts-arm64-all:arm64 -KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules/ +KERNELS := onl-kernel-5.10-lts-arm64-all:arm64 +KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules-5_10/ VENDOR := accton BASENAME := arm64-accton-as4224-52p ARCH := arm64 diff --git a/packages/platforms/accton/arm64/as4224/as4224-52p/platform-config/r0/src/lib/arm64-accton-as4224-52p-r0.yml b/packages/platforms/accton/arm64/as4224/as4224-52p/platform-config/r0/src/lib/arm64-accton-as4224-52p-r0.yml index ab28a69ca..1c347824a 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52p/platform-config/r0/src/lib/arm64-accton-as4224-52p-r0.yml +++ b/packages/platforms/accton/arm64/as4224/as4224-52p/platform-config/r0/src/lib/arm64-accton-as4224-52p-r0.yml @@ -9,10 +9,10 @@ arm64-accton-as4224-52p-r0: flat_image_tree: kernel: - <<: *arm64-kernel-5-15 + <<: *arm64-kernel-5-10 dtb: =: accton-as4224.dtb - <<: *arm64-kernel-5-15-package + <<: *arm64-kernel-5-10-package itb: <<: *arm64-itb diff --git a/packages/platforms/accton/arm64/as4224/as4224-52t/modules/PKG.yml b/packages/platforms/accton/arm64/as4224/as4224-52t/modules/PKG.yml index 182a421c6..13ca668ee 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52t/modules/PKG.yml +++ b/packages/platforms/accton/arm64/as4224/as4224-52t/modules/PKG.yml @@ -1 +1 @@ -!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4224-52t KERNELS="onl-kernel-5.15-lts-arm64-all:arm64" +!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4224-52t KERNELS="onl-kernel-5.10-lts-arm64-all:arm64" diff --git a/packages/platforms/accton/arm64/as4224/as4224-52t/modules/builds/Makefile b/packages/platforms/accton/arm64/as4224/as4224-52t/modules/builds/Makefile index 90803fafa..2a3cc488d 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52t/modules/builds/Makefile +++ b/packages/platforms/accton/arm64/as4224/as4224-52t/modules/builds/Makefile @@ -1,5 +1,5 @@ -KERNELS := onl-kernel-5.15-lts-arm64-all:arm64 -KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules/ +KERNELS := onl-kernel-5.10-lts-arm64-all:arm64 +KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules-5_10/ VENDOR := accton BASENAME := arm64-accton-as4224-52t ARCH := arm64 diff --git a/packages/platforms/accton/arm64/as4224/as4224-52t/platform-config/r0/src/lib/arm64-accton-as4224-52t-r0.yml b/packages/platforms/accton/arm64/as4224/as4224-52t/platform-config/r0/src/lib/arm64-accton-as4224-52t-r0.yml index 25ad136eb..3a6551a2e 100644 --- a/packages/platforms/accton/arm64/as4224/as4224-52t/platform-config/r0/src/lib/arm64-accton-as4224-52t-r0.yml +++ b/packages/platforms/accton/arm64/as4224/as4224-52t/platform-config/r0/src/lib/arm64-accton-as4224-52t-r0.yml @@ -9,10 +9,10 @@ arm64-accton-as4224-52t-r0: flat_image_tree: kernel: - <<: *arm64-kernel-5-15 + <<: *arm64-kernel-5-10 dtb: =: accton-as4224.dtb - <<: *arm64-kernel-5-15-package + <<: *arm64-kernel-5-10-package itb: <<: *arm64-itb diff --git a/packages/platforms/accton/arm64/as4224/as5114-48x/modules/PKG.yml b/packages/platforms/accton/arm64/as4224/as5114-48x/modules/PKG.yml index e45020b08..d8fb367c4 100644 --- a/packages/platforms/accton/arm64/as4224/as5114-48x/modules/PKG.yml +++ b/packages/platforms/accton/arm64/as4224/as5114-48x/modules/PKG.yml @@ -1 +1 @@ -!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as5114-48x KERNELS="onl-kernel-5.15-lts-arm64-all:arm64" +!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as5114-48x KERNELS="onl-kernel-5.10-lts-arm64-all:arm64" diff --git a/packages/platforms/accton/arm64/as4224/as5114-48x/modules/builds/Makefile b/packages/platforms/accton/arm64/as4224/as5114-48x/modules/builds/Makefile index b79dacd5a..2b9df99e6 100644 --- a/packages/platforms/accton/arm64/as4224/as5114-48x/modules/builds/Makefile +++ b/packages/platforms/accton/arm64/as4224/as5114-48x/modules/builds/Makefile @@ -1,5 +1,5 @@ -KERNELS := onl-kernel-5.15-lts-arm64-all:arm64 -KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules/ +KERNELS := onl-kernel-5.10-lts-arm64-all:arm64 +KMODULES := $(ONL)/packages/platforms/accton/arm64/as4224/src/modules-5_10/ VENDOR := accton BASENAME := arm64-accton-as5114-48x ARCH := arm64 diff --git a/packages/platforms/accton/arm64/as4224/as5114-48x/platform-config/r0/src/lib/arm64-accton-as5114-48x-r0.yml b/packages/platforms/accton/arm64/as4224/as5114-48x/platform-config/r0/src/lib/arm64-accton-as5114-48x-r0.yml index 0d5d8cc9a..1abce11db 100644 --- a/packages/platforms/accton/arm64/as4224/as5114-48x/platform-config/r0/src/lib/arm64-accton-as5114-48x-r0.yml +++ b/packages/platforms/accton/arm64/as4224/as5114-48x/platform-config/r0/src/lib/arm64-accton-as5114-48x-r0.yml @@ -9,10 +9,10 @@ arm64-accton-as5114-48x-r0: flat_image_tree: kernel: - <<: *arm64-kernel-5-15 + <<: *arm64-kernel-5-10 dtb: =: accton-as5114.dtb - <<: *arm64-kernel-5-15-package + <<: *arm64-kernel-5-10-package itb: <<: *arm64-itb diff --git a/packages/platforms/accton/arm64/as4224/src/modules/.gitignore b/packages/platforms/accton/arm64/as4224/src/modules-5_10/.gitignore similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/.gitignore rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/.gitignore diff --git a/packages/platforms/accton/arm64/as4224/src/modules/Makefile b/packages/platforms/accton/arm64/as4224/src/modules-5_10/Makefile similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/Makefile rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/Makefile diff --git a/packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-cpld.c b/packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-cpld.c similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-cpld.c rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-cpld.c diff --git a/packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-fan.c b/packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-fan.c similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-fan.c rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-fan.c diff --git a/packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-gpio-i2c.c b/packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-gpio-i2c.c similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-gpio-i2c.c rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-gpio-i2c.c diff --git a/packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-psu.c b/packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-psu.c similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/arm64-accton-as4224-psu.c rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/arm64-accton-as4224-psu.c diff --git a/packages/platforms/accton/arm64/as4224/src/modules/optoe.c b/packages/platforms/accton/arm64/as4224/src/modules-5_10/optoe.c similarity index 100% rename from packages/platforms/accton/arm64/as4224/src/modules/optoe.c rename to packages/platforms/accton/arm64/as4224/src/modules-5_10/optoe.c diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/.gitignore b/packages/platforms/accton/arm64/as4224/src/modules-5_6/.gitignore new file mode 100644 index 000000000..e35baed35 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/.gitignore @@ -0,0 +1,6 @@ +*.ko +*.mod* +*.o +*.a +Module.* +modules.order diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/Makefile b/packages/platforms/accton/arm64/as4224/src/modules-5_6/Makefile new file mode 100644 index 000000000..595bad396 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/Makefile @@ -0,0 +1,5 @@ +obj-m += arm64-accton-as4224-cpld.o +obj-m += arm64-accton-as4224-fan.o +obj-m += arm64-accton-as4224-psu.o +obj-m += arm64-accton-as4224-gpio-i2c.o +obj-m += optoe.o \ No newline at end of file diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-cpld.c b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-cpld.c new file mode 100644 index 000000000..b89d2b036 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-cpld.c @@ -0,0 +1,1518 @@ +/* + * Copyright (C) Brandon Chuang + * + * This module supports the accton cpld that hold the channel select + * mechanism for other i2c slave devices, such as SFP. + * This includes the: + * Accton as4224 CPLD1/CPLD2/CPLD3 + * + * Based on: + * pca954x.c from Kumar Gala + * Copyright (C) 2006 + * + * Based on: + * pca954x.c from Ken Harrenstien + * Copyright (C) 2004 Google, Inc. (Ken Harrenstien) + * + * Based on: + * i2c-virtual_cb.c from Brian Kuschak + * and + * pca9540.c from Jean Delvare . + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "as4224_cpld" + +#define I2C_RW_RETRY_COUNT 10 +#define I2C_RW_RETRY_INTERVAL 60 /* ms */ +#define BOARD_INFO_REG_OFFSET 0x00 +#define I2C_WRITE_REQUEST_REG 0xE0 +#define I2C_LOCK_BY_7040_VAL 0x10 +#define I2C_WRITE_REQUEST_7040_VAL 0x1 +#define I2C_WRITE_REQUEST_RETRY_TIMES 3 +#define WTD_RESET_GPIO_PIN_MPP3 35 + +static ssize_t show_module(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_module_48x(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_module_52x(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_present_all(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_rxlos_all(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t set_control_48x(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static ssize_t set_control_52x(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static ssize_t access(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static ssize_t show_version(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_platform_id(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t show_wtd(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t set_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static ssize_t reset_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static int as4224_cpld_read_internal(struct i2c_client *client, u8 reg); +static int as4224_cpld_write_internal(struct i2c_client *client, u8 reg, u8 value); +static ssize_t show_i2c_request(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t set_i2c_request(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); + +static LIST_HEAD(cpld_client_list); +static struct mutex list_lock; + +struct cpld_client_node { + struct i2c_client *client; + struct list_head list; +}; + +enum cpld_type { + as4224_cpld1 +}; + +enum as4224_platform_id { + AS5114_48X, + AS4224_52P, + AS4224_52T, + AS4224_52T_DAC, + PID_UNKNOWN +}; + +struct as4224_cpld_data { + enum cpld_type type; + struct device *hwmon_dev; + struct mutex update_lock; + enum as4224_platform_id platform_id; +}; + +static const struct i2c_device_id as4224_cpld_id[] = { + { "as4224_cpld1", as4224_cpld1 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, as4224_cpld_id); + +#define TRANSCEIVER_PRESENT_ATTR_ID(index) MODULE_PRESENT_##index +#define TRANSCEIVER_TXDISABLE_ATTR_ID(index) MODULE_TXDISABLE_##index +#define TRANSCEIVER_RXLOS_ATTR_ID(index) MODULE_RXLOS_##index +#define TRANSCEIVER_TXFAULT_ATTR_ID(index) MODULE_TXFAULT_##index + +enum as4224_cpld_sysfs_attributes { + PLATFORM_ID, + CPLD_VERSION, + ACCESS, + I2C_ACCESS_REQUEST_7040, + WTD_RESET_7040, /* Trigger GPIO to reset wtd timer */ + WTD_STATE_7040, /* Register 0x90 bit 1 */ + WTD_ENABLE_7040, /* Register 0x91 bit 1 */ + WTD_CLOCK_7040, /* Register 0x92 bit 7:6 */ + WTD_COUNTER_7040, /* Register 0x92 bit 5:0 */ + MODULE_PRESENT_ALL, + MODULE_RXLOS_ALL, + MODULE_COUNT, + MODULE_INDEX_BEGIN, + /* transceiver attributes */ + TRANSCEIVER_PRESENT_ATTR_ID(1), + TRANSCEIVER_PRESENT_ATTR_ID(2), + TRANSCEIVER_PRESENT_ATTR_ID(3), + TRANSCEIVER_PRESENT_ATTR_ID(4), + TRANSCEIVER_PRESENT_ATTR_ID(5), + TRANSCEIVER_PRESENT_ATTR_ID(6), + TRANSCEIVER_PRESENT_ATTR_ID(7), + TRANSCEIVER_PRESENT_ATTR_ID(8), + TRANSCEIVER_PRESENT_ATTR_ID(9), + TRANSCEIVER_PRESENT_ATTR_ID(10), + TRANSCEIVER_PRESENT_ATTR_ID(11), + TRANSCEIVER_PRESENT_ATTR_ID(12), + TRANSCEIVER_PRESENT_ATTR_ID(13), + TRANSCEIVER_PRESENT_ATTR_ID(14), + TRANSCEIVER_PRESENT_ATTR_ID(15), + TRANSCEIVER_PRESENT_ATTR_ID(16), + TRANSCEIVER_PRESENT_ATTR_ID(17), + TRANSCEIVER_PRESENT_ATTR_ID(18), + TRANSCEIVER_PRESENT_ATTR_ID(19), + TRANSCEIVER_PRESENT_ATTR_ID(20), + TRANSCEIVER_PRESENT_ATTR_ID(21), + TRANSCEIVER_PRESENT_ATTR_ID(22), + TRANSCEIVER_PRESENT_ATTR_ID(23), + TRANSCEIVER_PRESENT_ATTR_ID(24), + TRANSCEIVER_PRESENT_ATTR_ID(25), + TRANSCEIVER_PRESENT_ATTR_ID(26), + TRANSCEIVER_PRESENT_ATTR_ID(27), + TRANSCEIVER_PRESENT_ATTR_ID(28), + TRANSCEIVER_PRESENT_ATTR_ID(29), + TRANSCEIVER_PRESENT_ATTR_ID(30), + TRANSCEIVER_PRESENT_ATTR_ID(31), + TRANSCEIVER_PRESENT_ATTR_ID(32), + TRANSCEIVER_PRESENT_ATTR_ID(33), + TRANSCEIVER_PRESENT_ATTR_ID(34), + TRANSCEIVER_PRESENT_ATTR_ID(35), + TRANSCEIVER_PRESENT_ATTR_ID(36), + TRANSCEIVER_PRESENT_ATTR_ID(37), + TRANSCEIVER_PRESENT_ATTR_ID(38), + TRANSCEIVER_PRESENT_ATTR_ID(39), + TRANSCEIVER_PRESENT_ATTR_ID(40), + TRANSCEIVER_PRESENT_ATTR_ID(41), + TRANSCEIVER_PRESENT_ATTR_ID(42), + TRANSCEIVER_PRESENT_ATTR_ID(43), + TRANSCEIVER_PRESENT_ATTR_ID(44), + TRANSCEIVER_PRESENT_ATTR_ID(45), + TRANSCEIVER_PRESENT_ATTR_ID(46), + TRANSCEIVER_PRESENT_ATTR_ID(47), + TRANSCEIVER_PRESENT_ATTR_ID(48), + TRANSCEIVER_PRESENT_ATTR_ID(49), + TRANSCEIVER_PRESENT_ATTR_ID(50), + TRANSCEIVER_PRESENT_ATTR_ID(51), + TRANSCEIVER_PRESENT_ATTR_ID(52), + TRANSCEIVER_TXDISABLE_ATTR_ID(1), + TRANSCEIVER_TXDISABLE_ATTR_ID(2), + TRANSCEIVER_TXDISABLE_ATTR_ID(3), + TRANSCEIVER_TXDISABLE_ATTR_ID(4), + TRANSCEIVER_TXDISABLE_ATTR_ID(5), + TRANSCEIVER_TXDISABLE_ATTR_ID(6), + TRANSCEIVER_TXDISABLE_ATTR_ID(7), + TRANSCEIVER_TXDISABLE_ATTR_ID(8), + TRANSCEIVER_TXDISABLE_ATTR_ID(9), + TRANSCEIVER_TXDISABLE_ATTR_ID(10), + TRANSCEIVER_TXDISABLE_ATTR_ID(11), + TRANSCEIVER_TXDISABLE_ATTR_ID(12), + TRANSCEIVER_TXDISABLE_ATTR_ID(13), + TRANSCEIVER_TXDISABLE_ATTR_ID(14), + TRANSCEIVER_TXDISABLE_ATTR_ID(15), + TRANSCEIVER_TXDISABLE_ATTR_ID(16), + TRANSCEIVER_TXDISABLE_ATTR_ID(17), + TRANSCEIVER_TXDISABLE_ATTR_ID(18), + TRANSCEIVER_TXDISABLE_ATTR_ID(19), + TRANSCEIVER_TXDISABLE_ATTR_ID(20), + TRANSCEIVER_TXDISABLE_ATTR_ID(21), + TRANSCEIVER_TXDISABLE_ATTR_ID(22), + TRANSCEIVER_TXDISABLE_ATTR_ID(23), + TRANSCEIVER_TXDISABLE_ATTR_ID(24), + TRANSCEIVER_TXDISABLE_ATTR_ID(25), + TRANSCEIVER_TXDISABLE_ATTR_ID(26), + TRANSCEIVER_TXDISABLE_ATTR_ID(27), + TRANSCEIVER_TXDISABLE_ATTR_ID(28), + TRANSCEIVER_TXDISABLE_ATTR_ID(29), + TRANSCEIVER_TXDISABLE_ATTR_ID(30), + TRANSCEIVER_TXDISABLE_ATTR_ID(31), + TRANSCEIVER_TXDISABLE_ATTR_ID(32), + TRANSCEIVER_TXDISABLE_ATTR_ID(33), + TRANSCEIVER_TXDISABLE_ATTR_ID(34), + TRANSCEIVER_TXDISABLE_ATTR_ID(35), + TRANSCEIVER_TXDISABLE_ATTR_ID(36), + TRANSCEIVER_TXDISABLE_ATTR_ID(37), + TRANSCEIVER_TXDISABLE_ATTR_ID(38), + TRANSCEIVER_TXDISABLE_ATTR_ID(39), + TRANSCEIVER_TXDISABLE_ATTR_ID(40), + TRANSCEIVER_TXDISABLE_ATTR_ID(41), + TRANSCEIVER_TXDISABLE_ATTR_ID(42), + TRANSCEIVER_TXDISABLE_ATTR_ID(43), + TRANSCEIVER_TXDISABLE_ATTR_ID(44), + TRANSCEIVER_TXDISABLE_ATTR_ID(45), + TRANSCEIVER_TXDISABLE_ATTR_ID(46), + TRANSCEIVER_TXDISABLE_ATTR_ID(47), + TRANSCEIVER_TXDISABLE_ATTR_ID(48), + TRANSCEIVER_TXDISABLE_ATTR_ID(49), + TRANSCEIVER_TXDISABLE_ATTR_ID(50), + TRANSCEIVER_TXDISABLE_ATTR_ID(51), + TRANSCEIVER_TXDISABLE_ATTR_ID(52), + TRANSCEIVER_RXLOS_ATTR_ID(1), + TRANSCEIVER_RXLOS_ATTR_ID(2), + TRANSCEIVER_RXLOS_ATTR_ID(3), + TRANSCEIVER_RXLOS_ATTR_ID(4), + TRANSCEIVER_RXLOS_ATTR_ID(5), + TRANSCEIVER_RXLOS_ATTR_ID(6), + TRANSCEIVER_RXLOS_ATTR_ID(7), + TRANSCEIVER_RXLOS_ATTR_ID(8), + TRANSCEIVER_RXLOS_ATTR_ID(9), + TRANSCEIVER_RXLOS_ATTR_ID(10), + TRANSCEIVER_RXLOS_ATTR_ID(11), + TRANSCEIVER_RXLOS_ATTR_ID(12), + TRANSCEIVER_RXLOS_ATTR_ID(13), + TRANSCEIVER_RXLOS_ATTR_ID(14), + TRANSCEIVER_RXLOS_ATTR_ID(15), + TRANSCEIVER_RXLOS_ATTR_ID(16), + TRANSCEIVER_RXLOS_ATTR_ID(17), + TRANSCEIVER_RXLOS_ATTR_ID(18), + TRANSCEIVER_RXLOS_ATTR_ID(19), + TRANSCEIVER_RXLOS_ATTR_ID(20), + TRANSCEIVER_RXLOS_ATTR_ID(21), + TRANSCEIVER_RXLOS_ATTR_ID(22), + TRANSCEIVER_RXLOS_ATTR_ID(23), + TRANSCEIVER_RXLOS_ATTR_ID(24), + TRANSCEIVER_RXLOS_ATTR_ID(25), + TRANSCEIVER_RXLOS_ATTR_ID(26), + TRANSCEIVER_RXLOS_ATTR_ID(27), + TRANSCEIVER_RXLOS_ATTR_ID(28), + TRANSCEIVER_RXLOS_ATTR_ID(29), + TRANSCEIVER_RXLOS_ATTR_ID(30), + TRANSCEIVER_RXLOS_ATTR_ID(31), + TRANSCEIVER_RXLOS_ATTR_ID(32), + TRANSCEIVER_RXLOS_ATTR_ID(33), + TRANSCEIVER_RXLOS_ATTR_ID(34), + TRANSCEIVER_RXLOS_ATTR_ID(35), + TRANSCEIVER_RXLOS_ATTR_ID(36), + TRANSCEIVER_RXLOS_ATTR_ID(37), + TRANSCEIVER_RXLOS_ATTR_ID(38), + TRANSCEIVER_RXLOS_ATTR_ID(39), + TRANSCEIVER_RXLOS_ATTR_ID(40), + TRANSCEIVER_RXLOS_ATTR_ID(41), + TRANSCEIVER_RXLOS_ATTR_ID(42), + TRANSCEIVER_RXLOS_ATTR_ID(43), + TRANSCEIVER_RXLOS_ATTR_ID(44), + TRANSCEIVER_RXLOS_ATTR_ID(45), + TRANSCEIVER_RXLOS_ATTR_ID(46), + TRANSCEIVER_RXLOS_ATTR_ID(47), + TRANSCEIVER_RXLOS_ATTR_ID(48), + TRANSCEIVER_RXLOS_ATTR_ID(49), + TRANSCEIVER_RXLOS_ATTR_ID(50), + TRANSCEIVER_RXLOS_ATTR_ID(51), + TRANSCEIVER_RXLOS_ATTR_ID(52), + TRANSCEIVER_TXFAULT_ATTR_ID(1), + TRANSCEIVER_TXFAULT_ATTR_ID(2), + TRANSCEIVER_TXFAULT_ATTR_ID(3), + TRANSCEIVER_TXFAULT_ATTR_ID(4), + TRANSCEIVER_TXFAULT_ATTR_ID(5), + TRANSCEIVER_TXFAULT_ATTR_ID(6), + TRANSCEIVER_TXFAULT_ATTR_ID(7), + TRANSCEIVER_TXFAULT_ATTR_ID(8), + TRANSCEIVER_TXFAULT_ATTR_ID(9), + TRANSCEIVER_TXFAULT_ATTR_ID(10), + TRANSCEIVER_TXFAULT_ATTR_ID(11), + TRANSCEIVER_TXFAULT_ATTR_ID(12), + TRANSCEIVER_TXFAULT_ATTR_ID(13), + TRANSCEIVER_TXFAULT_ATTR_ID(14), + TRANSCEIVER_TXFAULT_ATTR_ID(15), + TRANSCEIVER_TXFAULT_ATTR_ID(16), + TRANSCEIVER_TXFAULT_ATTR_ID(17), + TRANSCEIVER_TXFAULT_ATTR_ID(18), + TRANSCEIVER_TXFAULT_ATTR_ID(19), + TRANSCEIVER_TXFAULT_ATTR_ID(20), + TRANSCEIVER_TXFAULT_ATTR_ID(21), + TRANSCEIVER_TXFAULT_ATTR_ID(22), + TRANSCEIVER_TXFAULT_ATTR_ID(23), + TRANSCEIVER_TXFAULT_ATTR_ID(24), + TRANSCEIVER_TXFAULT_ATTR_ID(25), + TRANSCEIVER_TXFAULT_ATTR_ID(26), + TRANSCEIVER_TXFAULT_ATTR_ID(27), + TRANSCEIVER_TXFAULT_ATTR_ID(28), + TRANSCEIVER_TXFAULT_ATTR_ID(29), + TRANSCEIVER_TXFAULT_ATTR_ID(30), + TRANSCEIVER_TXFAULT_ATTR_ID(31), + TRANSCEIVER_TXFAULT_ATTR_ID(32), + TRANSCEIVER_TXFAULT_ATTR_ID(33), + TRANSCEIVER_TXFAULT_ATTR_ID(34), + TRANSCEIVER_TXFAULT_ATTR_ID(35), + TRANSCEIVER_TXFAULT_ATTR_ID(36), + TRANSCEIVER_TXFAULT_ATTR_ID(37), + TRANSCEIVER_TXFAULT_ATTR_ID(38), + TRANSCEIVER_TXFAULT_ATTR_ID(39), + TRANSCEIVER_TXFAULT_ATTR_ID(40), + TRANSCEIVER_TXFAULT_ATTR_ID(41), + TRANSCEIVER_TXFAULT_ATTR_ID(42), + TRANSCEIVER_TXFAULT_ATTR_ID(43), + TRANSCEIVER_TXFAULT_ATTR_ID(44), + TRANSCEIVER_TXFAULT_ATTR_ID(45), + TRANSCEIVER_TXFAULT_ATTR_ID(46), + TRANSCEIVER_TXFAULT_ATTR_ID(47), + TRANSCEIVER_TXFAULT_ATTR_ID(48), + TRANSCEIVER_TXFAULT_ATTR_ID(49), + TRANSCEIVER_TXFAULT_ATTR_ID(50), + TRANSCEIVER_TXFAULT_ATTR_ID(51), + TRANSCEIVER_TXFAULT_ATTR_ID(52), +}; + +/* sysfs attributes for hwmon + */ +static SENSOR_DEVICE_ATTR(platform_id, S_IRUGO, show_platform_id, NULL, PLATFORM_ID); +static SENSOR_DEVICE_ATTR(version, S_IRUGO, show_version, NULL, CPLD_VERSION); +static SENSOR_DEVICE_ATTR(access, S_IWUSR, NULL, access, ACCESS); +static SENSOR_DEVICE_ATTR(wtd_reset_7040, S_IWUSR, NULL, reset_wtd, WTD_RESET_7040); +static SENSOR_DEVICE_ATTR(wtd_state_7040, S_IRUGO, show_wtd, NULL, WTD_STATE_7040); +static SENSOR_DEVICE_ATTR(wtd_enable_7040, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_ENABLE_7040); +static SENSOR_DEVICE_ATTR(wtd_clock_7040, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_CLOCK_7040); +static SENSOR_DEVICE_ATTR(wtd_counter_7040, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_COUNTER_7040); +static SENSOR_DEVICE_ATTR(module_present_all, S_IRUGO, show_present_all, NULL, MODULE_PRESENT_ALL); +static SENSOR_DEVICE_ATTR(module_rx_los_all, S_IRUGO, show_rxlos_all, NULL, MODULE_RXLOS_ALL); +static SENSOR_DEVICE_ATTR(module_count, S_IRUGO, show_module, NULL, MODULE_COUNT); +static SENSOR_DEVICE_ATTR(module_index_begin, S_IRUGO, show_module, NULL, MODULE_INDEX_BEGIN); +static SENSOR_DEVICE_ATTR(i2c_access_request_7040, S_IRUGO | S_IWUSR, show_i2c_request, set_i2c_request, I2C_ACCESS_REQUEST_7040); + +static struct attribute *cpld_attributes_common[] = { + &sensor_dev_attr_platform_id.dev_attr.attr, + &sensor_dev_attr_version.dev_attr.attr, + &sensor_dev_attr_access.dev_attr.attr, + &sensor_dev_attr_i2c_access_request_7040.dev_attr.attr, + &sensor_dev_attr_wtd_reset_7040.dev_attr.attr, + &sensor_dev_attr_wtd_state_7040.dev_attr.attr, + &sensor_dev_attr_wtd_enable_7040.dev_attr.attr, + &sensor_dev_attr_wtd_clock_7040.dev_attr.attr, + &sensor_dev_attr_wtd_counter_7040.dev_attr.attr, + &sensor_dev_attr_module_present_all.dev_attr.attr, + &sensor_dev_attr_module_rx_los_all.dev_attr.attr, + &sensor_dev_attr_module_count.dev_attr.attr, + &sensor_dev_attr_module_index_begin.dev_attr.attr, + NULL +}; + +#define CPLD_ATTRS_COMMON() { .attrs = cpld_attributes_common } + +#define MODULE_ATTRS(index) \ + static SENSOR_DEVICE_ATTR(module_present_##index, S_IRUGO, show_module_52x, NULL, MODULE_PRESENT_##index); \ + static SENSOR_DEVICE_ATTR(module_tx_disable_##index, S_IRUGO | S_IWUSR, show_module_52x, set_control_52x, MODULE_TXDISABLE_##index); \ + static SENSOR_DEVICE_ATTR(module_rx_los_##index, S_IRUGO, show_module_52x, NULL, MODULE_RXLOS_##index); \ + static SENSOR_DEVICE_ATTR(module_tx_fault_##index, S_IRUGO, show_module_52x, NULL, MODULE_TXFAULT_##index); \ + static struct attribute *module_attributes##index[] = { \ + &sensor_dev_attr_module_present_##index.dev_attr.attr, \ + &sensor_dev_attr_module_tx_disable_##index.dev_attr.attr, \ + &sensor_dev_attr_module_rx_los_##index.dev_attr.attr, \ + &sensor_dev_attr_module_tx_fault_##index.dev_attr.attr, \ + NULL \ + } + +MODULE_ATTRS(1); +MODULE_ATTRS(2); +MODULE_ATTRS(3); +MODULE_ATTRS(4); +MODULE_ATTRS(5); +MODULE_ATTRS(6); +MODULE_ATTRS(7); +MODULE_ATTRS(8); +MODULE_ATTRS(9); +MODULE_ATTRS(10); +MODULE_ATTRS(11); +MODULE_ATTRS(12); +MODULE_ATTRS(13); +MODULE_ATTRS(14); +MODULE_ATTRS(15); +MODULE_ATTRS(16); +MODULE_ATTRS(17); +MODULE_ATTRS(18); +MODULE_ATTRS(19); +MODULE_ATTRS(20); +MODULE_ATTRS(21); +MODULE_ATTRS(22); +MODULE_ATTRS(23); +MODULE_ATTRS(24); +MODULE_ATTRS(25); +MODULE_ATTRS(26); +MODULE_ATTRS(27); +MODULE_ATTRS(28); +MODULE_ATTRS(29); +MODULE_ATTRS(30); +MODULE_ATTRS(31); +MODULE_ATTRS(32); +MODULE_ATTRS(33); +MODULE_ATTRS(34); +MODULE_ATTRS(35); +MODULE_ATTRS(36); +MODULE_ATTRS(37); +MODULE_ATTRS(38); +MODULE_ATTRS(39); +MODULE_ATTRS(40); +MODULE_ATTRS(41); +MODULE_ATTRS(42); +MODULE_ATTRS(43); +MODULE_ATTRS(44); +MODULE_ATTRS(45); +MODULE_ATTRS(46); +MODULE_ATTRS(47); +MODULE_ATTRS(48); +MODULE_ATTRS(49); +MODULE_ATTRS(50); +MODULE_ATTRS(51); +MODULE_ATTRS(52); + +#define MODULE_ATTR_GROUP(index) { .attrs = module_attributes##index } + +static struct attribute_group cpld_group[] = { + CPLD_ATTRS_COMMON(), + MODULE_ATTR_GROUP(1), + MODULE_ATTR_GROUP(2), + MODULE_ATTR_GROUP(3), + MODULE_ATTR_GROUP(4), + MODULE_ATTR_GROUP(5), + MODULE_ATTR_GROUP(6), + MODULE_ATTR_GROUP(7), + MODULE_ATTR_GROUP(8), + MODULE_ATTR_GROUP(9), + MODULE_ATTR_GROUP(10), + MODULE_ATTR_GROUP(11), + MODULE_ATTR_GROUP(12), + MODULE_ATTR_GROUP(13), + MODULE_ATTR_GROUP(14), + MODULE_ATTR_GROUP(15), + MODULE_ATTR_GROUP(16), + MODULE_ATTR_GROUP(17), + MODULE_ATTR_GROUP(18), + MODULE_ATTR_GROUP(19), + MODULE_ATTR_GROUP(20), + MODULE_ATTR_GROUP(21), + MODULE_ATTR_GROUP(22), + MODULE_ATTR_GROUP(23), + MODULE_ATTR_GROUP(24), + MODULE_ATTR_GROUP(25), + MODULE_ATTR_GROUP(26), + MODULE_ATTR_GROUP(27), + MODULE_ATTR_GROUP(28), + MODULE_ATTR_GROUP(29), + MODULE_ATTR_GROUP(30), + MODULE_ATTR_GROUP(31), + MODULE_ATTR_GROUP(32), + MODULE_ATTR_GROUP(33), + MODULE_ATTR_GROUP(34), + MODULE_ATTR_GROUP(35), + MODULE_ATTR_GROUP(36), + MODULE_ATTR_GROUP(37), + MODULE_ATTR_GROUP(38), + MODULE_ATTR_GROUP(39), + MODULE_ATTR_GROUP(40), + MODULE_ATTR_GROUP(41), + MODULE_ATTR_GROUP(42), + MODULE_ATTR_GROUP(43), + MODULE_ATTR_GROUP(44), + MODULE_ATTR_GROUP(45), + MODULE_ATTR_GROUP(46), + MODULE_ATTR_GROUP(47), + MODULE_ATTR_GROUP(48), + MODULE_ATTR_GROUP(49), + MODULE_ATTR_GROUP(50), + MODULE_ATTR_GROUP(51), + MODULE_ATTR_GROUP(52), +}; + +static int get_platform_id(struct i2c_client *client) +{ + int status; + + status = as4224_cpld_read_internal(client, BOARD_INFO_REG_OFFSET); + if (status < 0) { + dev_dbg(&client->dev, "cpld reg (0x%x) err %d\n", client->addr, status); + return PID_UNKNOWN; + } + + if (status & 0x10) { + return AS4224_52T; + } + else if (status & 0x20) { + return AS4224_52T_DAC; + } + else if (status & 0x80) { + return AS5114_48X; + } + else { + return AS4224_52P; + } +} + +static ssize_t show_present_all(struct device *dev, struct device_attribute *da, + char *buf) +{ + int i, status; + u8 values[6] = {0}; + u8 regs_52x[] = {0x41}; + u8 regs_48x[] = {0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5}; + u8 *regs[] = {regs_48x, regs_52x, regs_52x, regs_52x}; + u8 size[] = {ARRAY_SIZE(regs_48x), ARRAY_SIZE(regs_52x), ARRAY_SIZE(regs_52x), ARRAY_SIZE(regs_52x)}; + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + for (i = 0; i < size[data->platform_id]; i++) { + status = as4224_cpld_read_internal(client, regs[data->platform_id][i]); + if (status < 0) { + goto exit; + } + + values[i] = ~(u8)status; + } + + mutex_unlock(&data->update_lock); + + /* Return values in order */ + if (data->platform_id == AS5114_48X) { + return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x\n", values[0], values[1], + values[2], values[3], + values[4], values[5]); + } + else { /* AS4224_52X */ + return sprintf(buf, "%.2x\n", values[0] & 0xF); + } + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t show_rxlos_all(struct device *dev, struct device_attribute *da, + char *buf) +{ + int i, status; + u8 values[6] = {0}; + u8 regs_52x[] = {0x40}; + u8 regs_mask_52x[] = {0x36}; + u8 regs_48x[] = {0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB}; + u8 regs_mask_48x[] = {0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB}; + u8 *regs[] = {regs_48x, regs_52x, regs_52x, regs_52x}; + u8 size[] = {ARRAY_SIZE(regs_48x), ARRAY_SIZE(regs_52x), ARRAY_SIZE(regs_52x), ARRAY_SIZE(regs_52x)}; + u8 *regs_mask[] = {regs_mask_48x, regs_mask_52x, regs_mask_52x, regs_mask_52x}; + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + for (i = 0; i < size[data->platform_id]; i++) { + /* Enable the interrupt to CPU */ + status = as4224_cpld_write_internal(client, regs_mask[data->platform_id][i], 0); + if (unlikely(status < 0)) { + goto exit; + } + + status = as4224_cpld_read_internal(client, regs[data->platform_id][i]); + if (status < 0) { + goto exit; + } + + values[i] = (u8)status; + } + + mutex_unlock(&data->update_lock); + + /* Return values in order */ + if (data->platform_id == AS5114_48X) { + return sprintf(buf, "%.2x %.2x %.2x %.2x %.2x %.2x\n", values[0], values[1], + values[2], values[3], + values[4], values[5]); + } + else { /* AS4224_52X */ + return sprintf(buf, "%.2x\n", values[0] & 0xF); + } + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t show_module_48x(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + int status = 0; + u8 reg = 0, mask = 0, invert = 0; + + switch (attr->index) { + case MODULE_PRESENT_1 ... MODULE_PRESENT_8: + invert = 1; + reg = 0xC0; + mask = 0x1 << (attr->index - MODULE_PRESENT_1); + break; + case MODULE_PRESENT_9 ... MODULE_PRESENT_16: + invert = 1; + reg = 0xC1; + mask = 0x1 << (attr->index - MODULE_PRESENT_9); + break; + case MODULE_PRESENT_17 ... MODULE_PRESENT_24: + invert = 1; + reg = 0xC2; + mask = 0x1 << (attr->index - MODULE_PRESENT_17); + break; + case MODULE_PRESENT_25 ... MODULE_PRESENT_32: + invert = 1; + reg = 0xC3; + mask = 0x1 << (attr->index - MODULE_PRESENT_25); + break; + case MODULE_PRESENT_33 ... MODULE_PRESENT_40: + invert = 1; + reg = 0xC4; + mask = 0x1 << (attr->index - MODULE_PRESENT_33); + break; + case MODULE_PRESENT_41 ... MODULE_PRESENT_48: + invert = 1; + reg = 0xC5; + mask = 0x1 << (attr->index - MODULE_PRESENT_41); + break; + case MODULE_TXDISABLE_1 ... MODULE_TXDISABLE_8: + reg = 0xC6; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_1); + break; + case MODULE_TXDISABLE_9 ... MODULE_TXDISABLE_16: + reg = 0xC7; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_9); + break; + case MODULE_TXDISABLE_17 ... MODULE_TXDISABLE_24: + reg = 0xC8; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_17); + break; + case MODULE_TXDISABLE_25 ... MODULE_TXDISABLE_32: + reg = 0xC9; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_25); + break; + case MODULE_TXDISABLE_33 ... MODULE_TXDISABLE_40: + reg = 0xCA; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_33); + break; + case MODULE_TXDISABLE_41 ... MODULE_TXDISABLE_48: + reg = 0xCB; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_41); + break; + case MODULE_RXLOS_1 ... MODULE_RXLOS_8: + reg = 0xA6; + mask = 0x1 << (attr->index - MODULE_RXLOS_1); + break; + case MODULE_RXLOS_9 ... MODULE_RXLOS_16: + reg = 0xA7; + mask = 0x1 << (attr->index - MODULE_RXLOS_9); + break; + case MODULE_RXLOS_17 ... MODULE_RXLOS_24: + reg = 0xA8; + mask = 0x1 << (attr->index - MODULE_RXLOS_17); + break; + case MODULE_RXLOS_25 ... MODULE_RXLOS_32: + reg = 0xA9; + mask = 0x1 << (attr->index - MODULE_RXLOS_25); + break; + case MODULE_RXLOS_33 ... MODULE_RXLOS_40: + reg = 0xAA; + mask = 0x1 << (attr->index - MODULE_RXLOS_33); + break; + case MODULE_RXLOS_41 ... MODULE_RXLOS_48: + reg = 0xAB; + mask = 0x1 << (attr->index - MODULE_RXLOS_41); + break; + case MODULE_TXFAULT_1 ... MODULE_TXFAULT_8: + reg = 0xA0; + mask = 0x1 << (attr->index - MODULE_TXFAULT_1); + break; + case MODULE_TXFAULT_9 ... MODULE_TXFAULT_16: + reg = 0xA1; + mask = 0x1 << (attr->index - MODULE_TXFAULT_9); + break; + case MODULE_TXFAULT_17 ... MODULE_TXFAULT_24: + reg = 0xA2; + mask = 0x1 << (attr->index - MODULE_TXFAULT_17); + break; + case MODULE_TXFAULT_25 ... MODULE_TXFAULT_32: + reg = 0xA3; + mask = 0x1 << (attr->index - MODULE_TXFAULT_25); + break; + case MODULE_TXFAULT_33 ... MODULE_TXFAULT_40: + reg = 0xA4; + mask = 0x1 << (attr->index - MODULE_TXFAULT_33); + break; + case MODULE_TXFAULT_41 ... MODULE_TXFAULT_48: + reg = 0xA5; + mask = 0x1 << (attr->index - MODULE_TXFAULT_41); + break; + default: + return 0; + } + + mutex_lock(&data->update_lock); + if (attr->index >= MODULE_TXFAULT_1 && attr->index <= MODULE_TXFAULT_48) { + int reg_mask = 0xB0 + ((attr->index - MODULE_TXFAULT_1) / 8); + status = as4224_cpld_read_internal(client, reg_mask); + if (unlikely(status < 0)) { + goto exit; + } + + /* Enable the interrupt to CPU */ + status = as4224_cpld_write_internal(client, reg_mask, status & (~mask)); + if (unlikely(status < 0)) { + goto exit; + } + } + + if (attr->index >= MODULE_RXLOS_1 && attr->index <= MODULE_RXLOS_48) { + int reg_mask = 0xB6 + ((attr->index - MODULE_RXLOS_1) / 8); + status = as4224_cpld_read_internal(client, reg_mask); + if (unlikely(status < 0)) { + goto exit; + } + + /* Enable the interrupt to CPU */ + status = as4224_cpld_write_internal(client, reg_mask, status & (~mask)); + if (unlikely(status < 0)) { + goto exit; + } + } + + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", invert ? !(status & mask) : !!(status & mask)); + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t show_module_52x(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + int status = 0; + u8 reg = 0, mask = 0, invert = 0; + + if (data->platform_id == AS5114_48X) { + return show_module_48x(dev, da, buf); + } + + switch (attr->index) { + case MODULE_PRESENT_49 ... MODULE_PRESENT_52: + invert = 1; + reg = 0x41; + mask = 0x1 << (attr->index - MODULE_PRESENT_49); + break; + case MODULE_RXLOS_49 ... MODULE_RXLOS_52: + reg = 0x40; + mask = 0x1 << (attr->index - MODULE_RXLOS_49); + break; + case MODULE_TXFAULT_49 ... MODULE_TXFAULT_52: + reg = 0x40; + mask = 0x10 << (attr->index - MODULE_TXFAULT_49); + break; + case MODULE_TXDISABLE_49 ... MODULE_TXDISABLE_52: + reg = 0x42; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_49); + break; + default: + return 0; + } + + mutex_lock(&data->update_lock); + if ((attr->index >= MODULE_TXFAULT_49 && attr->index <= MODULE_TXFAULT_52) || + (attr->index >= MODULE_TXDISABLE_49 && attr->index <= MODULE_TXDISABLE_52)) { + status = as4224_cpld_read_internal(client, 0x36); + if (unlikely(status < 0)) { + goto exit; + } + + /* Enable the interrupt to CPU */ + status = as4224_cpld_write_internal(client, 0x36, status & (~mask)); + if (unlikely(status < 0)) { + goto exit; + } + } + + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", invert ? !(status & mask) : !!(status & mask)); + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t show_module(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + int ret = 0; + + switch (attr->index) { + case MODULE_INDEX_BEGIN: + ret = (data->platform_id == AS5114_48X) ? 1: 49; + break; + case MODULE_COUNT: + ret = (data->platform_id == AS5114_48X) ? 48: 4; + break; + default: + return 0; + } + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t set_control_48x(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + long value; + int status; + u8 reg = 0, mask = 0; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + switch (attr->index) { + case MODULE_TXDISABLE_1 ... MODULE_TXDISABLE_8: + reg = 0xC6; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_1); + break; + case MODULE_TXDISABLE_9 ... MODULE_TXDISABLE_16: + reg = 0xC7; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_9); + break; + case MODULE_TXDISABLE_17 ... MODULE_TXDISABLE_24: + reg = 0xC8; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_17); + break; + case MODULE_TXDISABLE_25 ... MODULE_TXDISABLE_32: + reg = 0xC9; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_25); + break; + case MODULE_TXDISABLE_33 ... MODULE_TXDISABLE_40: + reg = 0xCA; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_33); + break; + case MODULE_TXDISABLE_41 ... MODULE_TXDISABLE_48: + reg = 0xCB; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_41); + break; + default: + return 0; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + + /* Update tx_disable status */ + if (value) { + value = (status | mask); + } + else { + value = (status & ~mask); + } + + /* Set value to CPLD */ + status = as4224_cpld_write_internal(client, reg, value); + if (unlikely(status < 0)) { + goto exit; + } + + mutex_unlock(&data->update_lock); + return count; + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static int i2c_write_request_begin(struct i2c_client *client) +{ + int status = 0; + int retry = 0; + + for (retry = 0; retry <= I2C_WRITE_REQUEST_RETRY_TIMES; retry++) { + /* Read current status */ + status = i2c_smbus_read_byte_data(client, I2C_WRITE_REQUEST_REG); + if (unlikely(status < 0)) { + continue; + } + + if (status & I2C_LOCK_BY_7040_VAL) { + return 0; /* I2C already lock by 7040, just return */ + } + + status |= I2C_WRITE_REQUEST_7040_VAL; + status = i2c_smbus_write_byte_data(client, I2C_WRITE_REQUEST_REG, status); + if (unlikely(status < 0)) { + continue; + } + + /* Read out to make sure if 7040 get the access right */ + msleep(50); + status = i2c_smbus_read_byte_data(client, I2C_WRITE_REQUEST_REG); + if (unlikely(status < 0)) { + continue; + } + + if (status & I2C_LOCK_BY_7040_VAL) { + return 0; + } + + status = -EBUSY; + + if (retry != I2C_WRITE_REQUEST_RETRY_TIMES) { + msleep(1000); + } + } + + return status; +} + +static int i2c_write_request_end(struct i2c_client *client) +{ + int status = 0; + + status = i2c_smbus_read_byte_data(client, I2C_WRITE_REQUEST_REG); + if (unlikely(status < 0)) { + return status; + } + + status &= ~I2C_WRITE_REQUEST_7040_VAL; + status = i2c_smbus_write_byte_data(client, I2C_WRITE_REQUEST_REG, status); + if (unlikely(status < 0)) { + return status; + } + + return 0; +} + +static ssize_t show_i2c_request(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int status = 0; + + status = i2c_smbus_read_byte_data(client, I2C_WRITE_REQUEST_REG); + if (unlikely(status < 0)) { + return status; + } + + return sprintf(buf, "%d\n", !!(status & I2C_LOCK_BY_7040_VAL)); +} + +static ssize_t set_i2c_request(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + long value; + int status = 0; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + if (value) { + status = i2c_write_request_begin(client); + } + else { + status = i2c_write_request_end(client); + } + + return status ? status : count; +} + +static ssize_t set_control_52x(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + long value; + int status; + u8 reg = 0, mask = 0; + + if (data->platform_id == AS5114_48X) { + return set_control_48x(dev, da, buf, count); + } + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + switch (attr->index) { + case MODULE_TXDISABLE_49 ... MODULE_TXDISABLE_52: + reg = 0x42; + mask = 0x1 << (attr->index - MODULE_TXDISABLE_49); + break; + default: + return 0; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + + /* Update tx_disable status */ + if (value) { + value = (status | mask); + } + else { + value = (status & ~mask); + } + + /* Set value to CPLD */ + status = as4224_cpld_write_internal(client, reg, value); + if (unlikely(status < 0)) { + goto exit; + } + + mutex_unlock(&data->update_lock); + return count; + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t show_wtd(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + int status = 0; + u8 reg = 0, mask = 0; + + switch (attr->index) { + case WTD_STATE_7040: + reg = 0x90; + mask = 0x2; + break; + case WTD_ENABLE_7040: + reg = 0x91; + mask = 0x10; + break; + case WTD_CLOCK_7040: + reg = 0x92; + mask = 0xC0; + break; + case WTD_COUNTER_7040: + reg = 0x92; + mask = 0x3F; + break; + default: + return 0; + } + + mutex_lock(&data->update_lock); + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + mutex_unlock(&data->update_lock); + + while (!(mask & 0x1)) { + status >>= 1; + mask >>= 1; + } + + return sprintf(buf, "%d\n", (status & mask)); +exit: + mutex_unlock(&data->update_lock); + return status; +} + +#define VALIDATE_WTD_VAL_RETURN(value, mask) {if (value & ~mask) return -EINVAL;} + +static ssize_t set_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + long value; + int status; + u8 reg = 0, mask = 0; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + switch (attr->index) { + case WTD_ENABLE_7040: + reg = 0x91; + mask = 0xD0; + value = ((!!value) | 0x8) << 4; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + case WTD_CLOCK_7040: + reg = 0x92; + mask = 0xC0; + value <<= 6; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + case WTD_COUNTER_7040: + reg = 0x92; + mask = 0x3F; + value &= mask; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + default: + return 0; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + + status = as4224_cpld_read_internal(client, reg); + if (unlikely(status < 0)) { + goto exit; + } + + /* Update wtd status */ + status = (value & mask) | (status & ~mask); + status = as4224_cpld_write_internal(client, reg, status); + if (unlikely(status < 0)) { + goto exit; + } + + mutex_unlock(&data->update_lock); + return count; + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static ssize_t reset_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + long value; + int status; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + if (!value) { + return count; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + + /* Set gpio as output and set value as 0->1 to reset wtd */ + gpio_direction_output(WTD_RESET_GPIO_PIN_MPP3, 0); + gpio_direction_output(WTD_RESET_GPIO_PIN_MPP3, 1); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t access(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int status; + u32 addr, val; + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + + if (sscanf(buf, "0x%x 0x%x", &addr, &val) != 2) { + return -EINVAL; + } + + if (addr > 0xFF || val > 0xFF) { + return -EINVAL; + } + + mutex_lock(&data->update_lock); + status = as4224_cpld_write_internal(client, addr, val); + if (unlikely(status < 0)) { + goto exit; + } + mutex_unlock(&data->update_lock); + return count; + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static void as4224_cpld_add_client(struct i2c_client *client) +{ + struct cpld_client_node *node = kzalloc(sizeof(struct cpld_client_node), GFP_KERNEL); + + if (!node) { + dev_dbg(&client->dev, "Can't allocate cpld_client_node (0x%x)\n", client->addr); + return; + } + + node->client = client; + + mutex_lock(&list_lock); + list_add(&node->list, &cpld_client_list); + mutex_unlock(&list_lock); +} + +static void as4224_cpld_remove_client(struct i2c_client *client) +{ + struct list_head *list_node = NULL; + struct cpld_client_node *cpld_node = NULL; + int found = 0; + + mutex_lock(&list_lock); + + list_for_each(list_node, &cpld_client_list) + { + cpld_node = list_entry(list_node, struct cpld_client_node, list); + + if (cpld_node->client == client) { + found = 1; + break; + } + } + + if (found) { + list_del(list_node); + kfree(cpld_node); + } + + mutex_unlock(&list_lock); +} + +static ssize_t show_version(struct device *dev, struct device_attribute *attr, char *buf) +{ + int val = 0; + struct i2c_client *client = to_i2c_client(dev); + + val = i2c_smbus_read_byte_data(client, 0x1); + + if (val < 0) { + dev_dbg(&client->dev, "cpld(0x%x) reg(0x1) err %d\n", client->addr, val); + } + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_platform_id(struct device *dev, struct device_attribute *da, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct as4224_cpld_data *data = i2c_get_clientdata(client); + return sprintf(buf, "%d\n", data->platform_id); +} + +/* + * I2C init/probing/exit functions + */ +static int as4224_cpld_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); + struct as4224_cpld_data *data; + int ret = -ENODEV; + int i = 0; + u64 port_map[4] = { [AS5114_48X] = 0xFFFFFFFFFFFF, + [AS4224_52P] = 0xF000000000000, + [AS4224_52T] = 0xF000000000000, + [AS4224_52T_DAC] = 0xF000000000000}; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) + goto exit; + + data = kzalloc(sizeof(struct as4224_cpld_data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + data->type = id->driver_data; + data->platform_id = get_platform_id(client); + + if (data->platform_id == PID_UNKNOWN) { + ret = -ENODEV; + goto exit_free; + } + + /* Register sysfs hooks */ + for (i = 0; i < ARRAY_SIZE(cpld_group); i++) { + /* index 0 is for common attriute, register anyway */ + if ((i != 0) && !(port_map[data->platform_id] & BIT(i-1))) { + continue; + } + + ret = sysfs_create_group(&client->dev.kobj, &cpld_group[i]); + if (ret) { + goto exit_free; + } + } + + ret = gpio_request(WTD_RESET_GPIO_PIN_MPP3, "wtd_reset_7040"); + if (ret) { + dev_err(&client->dev, "Failed to request MPP3 gpio\n"); + goto exit_free; + } + + as4224_cpld_add_client(client); + return 0; + +exit_free: + kfree(data); +exit: + for (--i; i >= 0; i--) { + sysfs_remove_group(&client->dev.kobj, &cpld_group[i]); + } + return ret; +} + +static int as4224_cpld_remove(struct i2c_client *client) +{ + int i = 0; + struct as4224_cpld_data *data = i2c_get_clientdata(client); + as4224_cpld_remove_client(client); + gpio_free(WTD_RESET_GPIO_PIN_MPP3); + + for (i = 0; i < ARRAY_SIZE(cpld_group); i++) { + sysfs_remove_group(&client->dev.kobj, &cpld_group[i]); + } + + kfree(data); + return 0; +} + +static int as4224_cpld_read_internal(struct i2c_client *client, u8 reg) +{ + int status = 0, retry = I2C_RW_RETRY_COUNT; + + while (retry) { + status = i2c_smbus_read_byte_data(client, reg); + if (unlikely(status < 0)) { + msleep(I2C_RW_RETRY_INTERVAL); + retry--; + continue; + } + + break; + } + + return status; +} + +static int as4224_cpld_write_internal(struct i2c_client *client, u8 reg, u8 value) +{ + int status = 0, retry = I2C_RW_RETRY_COUNT; + + status = i2c_write_request_begin(client); + if (unlikely(status < 0)) { + return status; + } + + while (retry) { + status = i2c_smbus_write_byte_data(client, reg, value); + if (unlikely(status < 0)) { + msleep(I2C_RW_RETRY_INTERVAL); + retry--; + continue; + } + + break; + } + + status = i2c_write_request_end(client); + if (unlikely(status < 0)) { + return status; + } + + return status; +} + +int as4224_cpld_read(unsigned short cpld_addr, u8 reg) +{ + struct list_head *list_node = NULL; + struct cpld_client_node *cpld_node = NULL; + int ret = -EPERM; + + mutex_lock(&list_lock); + + list_for_each(list_node, &cpld_client_list) + { + cpld_node = list_entry(list_node, struct cpld_client_node, list); + + if (cpld_node->client->addr == cpld_addr) { + ret = as4224_cpld_read_internal(cpld_node->client, reg); + break; + } + } + + mutex_unlock(&list_lock); + + return ret; +} +EXPORT_SYMBOL(as4224_cpld_read); + +int as4224_cpld_write(unsigned short cpld_addr, u8 reg, u8 value) +{ + struct list_head *list_node = NULL; + struct cpld_client_node *cpld_node = NULL; + int ret = -EIO; + + mutex_lock(&list_lock); + + list_for_each(list_node, &cpld_client_list) + { + cpld_node = list_entry(list_node, struct cpld_client_node, list); + + if (cpld_node->client->addr == cpld_addr) { + ret = as4224_cpld_write_internal(cpld_node->client, reg, value); + break; + } + } + + mutex_unlock(&list_lock); + + return ret; +} +EXPORT_SYMBOL(as4224_cpld_write); + +static struct i2c_driver as4224_cpld_driver = { + .driver = { + .name = "as4224_cpld", + .owner = THIS_MODULE, + }, + .probe = as4224_cpld_probe, + .remove = as4224_cpld_remove, + .id_table = as4224_cpld_id, +}; + +static int __init as4224_cpld_init(void) +{ + mutex_init(&list_lock); + return i2c_add_driver(&as4224_cpld_driver); +} + +static void __exit as4224_cpld_exit(void) +{ + i2c_del_driver(&as4224_cpld_driver); +} + +MODULE_AUTHOR("Brandon Chuang "); +MODULE_DESCRIPTION("as4224_cpld driver"); +MODULE_LICENSE("GPL"); + +module_init(as4224_cpld_init); +module_exit(as4224_cpld_exit); diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-fan.c b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-fan.c new file mode 100644 index 000000000..11c0cdc21 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-fan.c @@ -0,0 +1,601 @@ +/* + * A hwmon driver for the Accton as9926 24d fan + * + * Copyright (C) 2016 Accton Technology Corporation. + * Brandon Chuang + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "as4224_fan" + +#define BOARD_INFO_REG_OFFSET 0x00 +#define FAN_STATUS_I2C_ADDR 0x40 +#define MAX_FAN_SPEED_RPM 20500 +#define FAN_DUTY_CYCLE_REG_MASK 0xFF +#define FAN_MAX_DUTY_CYCLE 100 +#define FAN_REG_VAL_TO_SPEED_RPM_STEP 100 + +static struct as4224_fan_data *as4224_fan_update_device(struct device *dev); +static ssize_t fan_show_value(struct device *dev, struct device_attribute *da, char *buf); +static ssize_t set_duty_cycle(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static ssize_t show_wtd(struct device *dev, struct device_attribute *da, + char *buf); +static ssize_t set_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +static int _reset_wtd(void); +static ssize_t reset_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count); +extern int as4224_cpld_read(unsigned short cpld_addr, u8 reg); +extern int as4224_cpld_write(unsigned short cpld_addr, u8 reg, u8 value); + +static struct as4224_fan_data *data = NULL; + +/* fan related data, the index should match sysfs_fan_attributes + */ +static const u8 fan_reg[] = { + 0x70, /* fan1 PWM */ + 0x71, /* fan2 PWM */ + 0x72, /* fan3 PWM */ + 0x73, /* fan4 PWM */ + 0x74, /* fan5 PWM */ + 0x75, /* fan6 PWM */ + 0x80, /* fan 1 tach speed */ + 0x81, /* fan 2 tach speed */ + 0x82, /* fan 3 tach speed */ + 0x83, /* fan 4 tach speed */ + 0x84, /* fan 5 tach speed */ + 0x85, /* fan 6 tach speed */ + 0x62, /* fan tech speed setting */ +}; + +/* fan data */ +struct as4224_fan_data { + struct platform_device *pdev; + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* != 0 if registers are valid */ + unsigned long last_updated; /* In jiffies */ + u8 reg_val[ARRAY_SIZE(fan_reg)]; /* Register value */ + u8 fan_count; +}; + +enum fan_id { + FAN1_ID, + FAN2_ID, + FAN3_ID, + FAN4_ID, + FAN5_ID, + FAN6_ID +}; + +enum sysfs_fan_attributes { + FAN1_PWM, + FAN2_PWM, + FAN3_PWM, + FAN4_PWM, + FAN5_PWM, + FAN6_PWM, + FAN1_SPEED_RPM, + FAN2_SPEED_RPM, + FAN3_SPEED_RPM, + FAN4_SPEED_RPM, + FAN5_SPEED_RPM, + FAN6_SPEED_RPM, + FAN_TECH_SETTING, + FAN1_FAULT, + FAN2_FAULT, + FAN3_FAULT, + FAN4_FAULT, + FAN5_FAULT, + FAN6_FAULT, + FAN_MAX_RPM, + FAN_COUNT, + WTD_CLOCK, + WTD_COUNTER, + WTD_ENABLE, + WTD_RESET +}; + +/* sysfs attributes for hwmon + */ +static SENSOR_DEVICE_ATTR(fan_max_rpm, S_IRUGO, fan_show_value, NULL, FAN_MAX_RPM); +static SENSOR_DEVICE_ATTR(fan_count, S_IRUGO, fan_show_value, NULL, FAN_COUNT); + +/* Fan watchdog */ +static SENSOR_DEVICE_ATTR(wtd_clock, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_CLOCK); +static SENSOR_DEVICE_ATTR(wtd_counter, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_COUNTER); +static SENSOR_DEVICE_ATTR(wtd_enable, S_IRUGO | S_IWUSR, show_wtd, set_wtd, WTD_ENABLE); +static SENSOR_DEVICE_ATTR(wtd_reset, S_IWUSR, NULL, reset_wtd, WTD_RESET); + +static struct attribute *fan_attributes_common[] = { + &sensor_dev_attr_fan_max_rpm.dev_attr.attr, + &sensor_dev_attr_fan_count.dev_attr.attr, + &sensor_dev_attr_wtd_clock.dev_attr.attr, + &sensor_dev_attr_wtd_counter.dev_attr.attr, + &sensor_dev_attr_wtd_enable.dev_attr.attr, + &sensor_dev_attr_wtd_reset.dev_attr.attr, + NULL +}; + +#define FAN_ATTRS_COMMON() { .attrs = fan_attributes_common } + +#define FAN_ATTRS(fid) \ + static SENSOR_DEVICE_ATTR(fan##fid##_duty_cycle_percentage, S_IWUSR | S_IRUGO, fan_show_value, set_duty_cycle, FAN##fid##_PWM); \ + static SENSOR_DEVICE_ATTR(fan##fid##_input, S_IRUGO, fan_show_value, NULL, FAN##fid##_SPEED_RPM); \ + static SENSOR_DEVICE_ATTR(fan##fid##_fault, S_IRUGO, fan_show_value, NULL, FAN##fid##_FAULT); \ + static struct attribute *fan_attributes##fid[] = { \ + &sensor_dev_attr_fan##fid##_duty_cycle_percentage.dev_attr.attr, \ + &sensor_dev_attr_fan##fid##_input.dev_attr.attr, \ + &sensor_dev_attr_fan##fid##_fault.dev_attr.attr, \ + NULL \ + } + +FAN_ATTRS(1); +FAN_ATTRS(2); +FAN_ATTRS(3); +FAN_ATTRS(4); +FAN_ATTRS(5); +FAN_ATTRS(6); + +#define FAN_ATTR_GROUP(fid) { .attrs = fan_attributes##fid } + +static struct attribute_group fan_group[] = { + FAN_ATTRS_COMMON(), + FAN_ATTR_GROUP(1), + FAN_ATTR_GROUP(2), + FAN_ATTR_GROUP(3), + FAN_ATTR_GROUP(4), + FAN_ATTR_GROUP(5), + FAN_ATTR_GROUP(6), +}; + +static int as4224_fan_read_value(u8 reg) +{ + return as4224_cpld_read(FAN_STATUS_I2C_ADDR, reg); +} + +static int as4224_fan_write_value(u8 reg, u8 value) +{ + return as4224_cpld_write(FAN_STATUS_I2C_ADDR, reg, value); +} + +/* fan utility functions + */ +static u32 reg_val_to_duty_cycle(u8 reg_val) +{ + reg_val &= FAN_DUTY_CYCLE_REG_MASK; + return (reg_val == 0xFF) ? 100 : (((u32)reg_val) * 100 / 255) + 1; +} + +static u8 duty_cycle_to_reg_val(u8 duty_cycle) +{ + if (duty_cycle == 0) { + return 0; + } + else if (duty_cycle > FAN_MAX_DUTY_CYCLE) { + duty_cycle = FAN_MAX_DUTY_CYCLE; + } + + return ((u32)duty_cycle) * 255 / 100; +} + +static u32 reg_val_to_speed_rpm(u8 reg_val, u8 tech_reg_val) +{ + u32 timer[] = { 1048, 2097, 4194, 8389 }; + u8 counter = (tech_reg_val & 0x3F); + u8 clock = (tech_reg_val >> 6) & 0x3; + + return (reg_val * 3000000) / (timer[clock] * counter); +} + +static u8 is_fan_fault(struct as4224_fan_data *data, enum fan_id id) +{ + return !reg_val_to_speed_rpm(data->reg_val[FAN1_SPEED_RPM + id], + data->reg_val[FAN_TECH_SETTING]); +} + +static ssize_t set_duty_cycle(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int error, value; + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + + error = kstrtoint(buf, 10, &value); + if (error) { + return error; + } + + if (value < 0 || value > FAN_MAX_DUTY_CYCLE) { + return -EINVAL; + } + + /* Disable the watchdog timer + */ + error = _reset_wtd(); + if (unlikely(error < 0)) { + dev_dbg(dev, "Unable to reset the watchdog timer\n"); + return error; + } + + as4224_fan_write_value(fan_reg[attr->index - FAN1_PWM], duty_cycle_to_reg_val(value)); + data->valid = 0; + + return count; +} + +static ssize_t fan_show_value(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct as4224_fan_data *data = as4224_fan_update_device(dev); + ssize_t ret = 0; + + if (data->valid) { + switch (attr->index) { + case FAN1_PWM: + case FAN2_PWM: + case FAN3_PWM: + case FAN4_PWM: + case FAN5_PWM: + case FAN6_PWM: + ret = sprintf(buf, "%u\n", reg_val_to_duty_cycle(data->reg_val[attr->index])); + break; + case FAN1_SPEED_RPM: + case FAN2_SPEED_RPM: + case FAN3_SPEED_RPM: + case FAN4_SPEED_RPM: + case FAN5_SPEED_RPM: + case FAN6_SPEED_RPM: + ret = sprintf(buf, "%u\n", reg_val_to_speed_rpm(data->reg_val[attr->index], + data->reg_val[FAN_TECH_SETTING])); + break; + case FAN1_FAULT: + case FAN2_FAULT: + case FAN3_FAULT: + case FAN4_FAULT: + case FAN5_FAULT: + case FAN6_FAULT: + ret = sprintf(buf, "%d\n", is_fan_fault(data, attr->index - FAN1_FAULT)); + break; + case FAN_MAX_RPM: + ret = sprintf(buf, "%d\n", MAX_FAN_SPEED_RPM); + break; + case FAN_COUNT: + ret = sprintf(buf, "%d\n", data->fan_count); + break; + default: + break; + } + } + + return ret; +} + +static ssize_t show_wtd(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + int status = 0; + u8 reg = 0, mask = 0; + + switch (attr->index) { + case WTD_ENABLE: + reg = 0x60; + mask = 0x01; + break; + case WTD_CLOCK: + reg = 0x61; + mask = 0xC0; + break; + case WTD_COUNTER: + reg = 0x61; + mask = 0x3F; + break; + default: + return 0; + } + + mutex_lock(&data->update_lock); + status = as4224_fan_read_value(reg); + if (unlikely(status < 0)) { + goto exit; + } + mutex_unlock(&data->update_lock); + + while (!(mask & 0x1)) { + status >>= 1; + mask >>= 1; + } + + return sprintf(buf, "%d\n", (status & mask)); +exit: + mutex_unlock(&data->update_lock); + return status; +} + +#define VALIDATE_WTD_VAL_RETURN(value, mask) {if (value & ~mask) return -EINVAL;} + +static ssize_t set_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + long value; + int status; + u8 reg = 0, mask = 0; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + switch (attr->index) { + case WTD_ENABLE: + reg = 0x60; + mask = 0x01; + value &= mask; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + case WTD_CLOCK: + reg = 0x61; + mask = 0xC0; + value <<= 6; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + case WTD_COUNTER: + reg = 0x61; + mask = 0x3F; + value &= mask; + VALIDATE_WTD_VAL_RETURN(value, mask); + break; + default: + return 0; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + + status = as4224_fan_read_value(reg); + if (unlikely(status < 0)) { + goto exit; + } + + /* Update wtd status */ + status = (value & mask) | (status & ~mask); + status = as4224_fan_write_value(reg, status); + if (unlikely(status < 0)) { + goto exit; + } + + mutex_unlock(&data->update_lock); + return count; + +exit: + mutex_unlock(&data->update_lock); + return status; +} + +static int _reset_wtd(void) +{ + int status; + + /* Set value as 0->1 to reset wtd */ + status = as4224_fan_write_value(0x60, 0); + if (unlikely(status < 0)) { + return status; + } + + msleep(50); + status = as4224_fan_write_value(0x60, 1); + if (unlikely(status < 0)) { + return status; + } + + return status; +} + +static ssize_t reset_wtd(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + long value; + int status; + + status = kstrtol(buf, 10, &value); + if (status) { + return status; + } + + if (!value) { + return count; + } + + /* Read current status */ + mutex_lock(&data->update_lock); + + status = _reset_wtd(); + if (unlikely(status < 0)) { + dev_dbg(dev, "Unable to reset the watchdog timer\n"); + return status; + } + + mutex_unlock(&data->update_lock); + return count; +} + +static struct as4224_fan_data *as4224_fan_update_device(struct device *dev) +{ + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || + !data->valid) { + int i; + + dev_dbg(dev, "Starting as4224_fan update\n"); + data->valid = 0; + + /* Update fan data + */ + for (i = 0; i < ARRAY_SIZE(data->reg_val); i++) { + int status = as4224_fan_read_value(fan_reg[i]); + + if (status < 0) { + data->valid = 0; + mutex_unlock(&data->update_lock); + dev_dbg(dev, "reg %d, err %d\n", fan_reg[i], status); + return data; + } + else { + data->reg_val[i] = status; + } + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static int as4224_fan_get_fan_count(void) +{ + int status; + + status = as4224_fan_read_value(BOARD_INFO_REG_OFFSET); + if (status < 0) { + return 0; + } + + if ((status & 0x10) || (status & 0x20)) { + return 4; + } + else if (status & 0x80) { + return 5; + } + else { + return 6; + } +} + +static int as4224_fan_probe(struct platform_device *pdev) +{ + int status = -1; + int i = 0; + + /* Register sysfs hooks */ + for (i = 0; i < (data->fan_count + 1); i++) { + /* Register sysfs hooks */ + status = sysfs_create_group(&pdev->dev.kobj, &fan_group[i]); + if (status) { + goto exit; + } + } + + dev_info(&pdev->dev, "device created\n"); + + return 0; + +exit: + for (--i; i >= 0; i--) { + sysfs_remove_group(&pdev->dev.kobj, &fan_group[i]); + } + return status; +} + +static int as4224_fan_remove(struct platform_device *pdev) +{ + int i = 0; + + for (i = 0; i < (data->fan_count + 1); i++) { + sysfs_remove_group(&pdev->dev.kobj, &fan_group[i]); + } + + return 0; +} + +static struct platform_driver as4224_fan_driver = { + .probe = as4224_fan_probe, + .remove = as4224_fan_remove, + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init as4224_fan_init(void) +{ + int ret; + + data = kzalloc(sizeof(struct as4224_fan_data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto alloc_err; + } + + mutex_init(&data->update_lock); + + data->fan_count = as4224_fan_get_fan_count(); + if (!data->fan_count) { + return -EIO; + } + + ret = platform_driver_register(&as4224_fan_driver); + if (ret < 0) { + goto dri_reg_err; + } + + data->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(data->pdev)) { + ret = PTR_ERR(data->pdev); + goto dev_reg_err; + } + + return 0; + +dev_reg_err: + platform_driver_unregister(&as4224_fan_driver); +dri_reg_err: + kfree(data); +alloc_err: + return ret; +} + +static void __exit as4224_fan_exit(void) +{ + platform_device_unregister(data->pdev); + platform_driver_unregister(&as4224_fan_driver); + kfree(data); +} + +module_init(as4224_fan_init); +module_exit(as4224_fan_exit); + +MODULE_AUTHOR("Brandon Chuang "); +MODULE_DESCRIPTION("as4224_fan driver"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-gpio-i2c.c b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-gpio-i2c.c new file mode 100644 index 000000000..4221d80a2 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-gpio-i2c.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_I2C_NGPIOS 144 + +#define GPIO_I2C_RETRY_COUNT 10 +#define GPIO_I2C_RETRY_INTERVAL 60 /* ms */ + +extern int as4224_cpld_read(unsigned short cpld_addr, u8 reg); +extern int as4224_cpld_write(unsigned short cpld_addr, u8 reg, u8 value); + +struct gpio_i2c_reg { + struct list_head head; + u32 gpio_num; + u32 reg_addr; + u32 reg_mask; +}; + +struct gpio_i2c_chip { + struct gpio_chip gpio_chip; + struct device *dev; + + struct list_head gpio_list; +}; + +static int gpio_i2c_read(struct gpio_i2c_chip *chip, u8 reg) +{ + int status = 0, retry = GPIO_I2C_RETRY_COUNT; + + while (retry) { + status = as4224_cpld_read(0x40, reg); + if (unlikely(status < 0)) { + msleep(GPIO_I2C_RETRY_INTERVAL); + retry--; + continue; + } + break; + } + + return status; +} + +static int gpio_i2c_write(struct gpio_i2c_chip *chip, u8 reg, u8 value) +{ + int status = 0, retry = GPIO_I2C_RETRY_COUNT; + + while (retry) { + status = as4224_cpld_write(0x40, reg, value); + if (unlikely(status < 0)) { + msleep(GPIO_I2C_RETRY_INTERVAL); + retry--; + continue; + } + break; + } + + return status; +} + +static struct gpio_i2c_reg *gpio_i2c_reg_by_num(struct gpio_i2c_chip *chip, + unsigned int offs) +{ + struct gpio_i2c_reg *gpio_reg; + + list_for_each_entry(gpio_reg, &chip->gpio_list, head) { + if (gpio_reg->gpio_num == offs) + return gpio_reg; + } + + return NULL; +} + +static int gpio_i2c_get_value(struct gpio_chip *gc, unsigned int offs) +{ + struct gpio_i2c_chip *chip = gpiochip_get_data(gc); + struct gpio_i2c_reg *gpio_reg; + int val; + + gpio_reg = gpio_i2c_reg_by_num(chip, offs); + if (!gpio_reg) { + dev_err(chip->dev, "invalid gpio offset (0x%x)\n", offs); + return -EINVAL; + } + + val = gpio_i2c_read(chip, gpio_reg->reg_addr); + val &= gpio_reg->reg_mask; + + return val; +} + +static void gpio_i2c_set_value(struct gpio_chip *gc, unsigned int offs, int set) +{ + struct gpio_i2c_chip *chip = gpiochip_get_data(gc); + struct gpio_i2c_reg *gpio_reg; + unsigned int val; + + gpio_reg = gpio_i2c_reg_by_num(chip, offs); + if (!gpio_reg) { + dev_err(chip->dev, "invalid gpio offset (0x%x)\n", offs); + return; + } + + val = gpio_i2c_read(chip, gpio_reg->reg_addr); + + val &= ~gpio_reg->reg_mask; + if (set) + val |= gpio_reg->reg_mask; + + gpio_i2c_write(chip, gpio_reg->reg_addr, val); +} + +static int gpio_i2c_reg_parse(struct gpio_i2c_chip *chip, struct device_node *np) +{ + struct gpio_i2c_reg *gpio_reg; + struct device *dev = chip->dev; + u32 gpio_reg_map[2]; + u32 gpio_num; + int err; + + err = of_property_read_u32_array(np, "reg-map", gpio_reg_map, + ARRAY_SIZE(gpio_reg_map)); + + if (err) { + dev_err(dev, "error while parsing 'reg-map' property\n"); + return err; + } + + err = of_property_read_u32(np, "gpio-num", &gpio_num); + if (err) { + dev_err(dev, "error while parsing 'gpio-num' property\n"); + return err; + } + + gpio_reg = devm_kmalloc(dev, sizeof(*gpio_reg), GFP_KERNEL); + if (!gpio_reg) + return err; + + gpio_reg->reg_addr = gpio_reg_map[0]; + gpio_reg->reg_mask = gpio_reg_map[1]; + gpio_reg->gpio_num = gpio_num; + + list_add(&gpio_reg->head, &chip->gpio_list); + + return 0; +} + +static int gpio_i2c_map_parse(struct gpio_i2c_chip *chip, + struct device_node *gpio_map_np) +{ + struct device_node *node; + + for_each_child_of_node(gpio_map_np, node) { + int err; + + err = gpio_i2c_reg_parse(chip, node); + if (err) + return err; + } + + return 0; +} + +static int gpio_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *gpio_map_np; + struct gpio_i2c_chip *chip; + int err; + + if (!np) + return -EINVAL; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + INIT_LIST_HEAD(&chip->gpio_list); + chip->dev = dev; + + gpio_map_np = of_find_node_by_name(np, "gpio-map"); + if (gpio_map_np) { + err = gpio_i2c_map_parse(chip, gpio_map_np); + if (err) + return err; + } + + chip->gpio_chip.parent = dev; + + dev_set_drvdata(dev, chip); + + chip->gpio_chip.label = dev_name(dev); + chip->gpio_chip.get = gpio_i2c_get_value; + chip->gpio_chip.set = gpio_i2c_set_value; + chip->gpio_chip.base = -1; + + chip->gpio_chip.ngpio = GPIO_I2C_NGPIOS; + + chip->gpio_chip.owner = THIS_MODULE; + chip->gpio_chip.can_sleep = true; + + return devm_gpiochip_add_data(dev, &chip->gpio_chip, chip); +} + +static int gpio_i2c_remove(struct platform_device *pdev) +{ + return 0; +} + + +static const struct of_device_id gpio_i2c_id[] = { + { .compatible = "gpio-i2c" }, + { } +}; +MODULE_DEVICE_TABLE(of, gpio_i2c_id); + +static struct platform_driver gpio_i2c_driver = { + .driver = { + .name = "gpio-i2c", + .owner = THIS_MODULE, + .of_match_table = gpio_i2c_id, + }, + .probe = gpio_i2c_probe, + .remove = gpio_i2c_remove, +}; + +module_platform_driver(gpio_i2c_driver); + + +MODULE_AUTHOR("Vadym Kochan "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("GPIO to I2C driver"); diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-psu.c b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-psu.c new file mode 100644 index 000000000..e64294a43 --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/arm64-accton-as4224-psu.c @@ -0,0 +1,327 @@ +/* + * An hwmon driver for accton as4224 Power Module + * + * Copyright (C) 2014 Accton Technology Corporation. + * Brandon Chuang + * + * Based on ad7414.c + * Copyright 2006 Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "as4224_psu" + +#define BOARD_INFO_REG_OFFSET 0x00 +#define PSU_STATUS_I2C_ADDR 0x40 +#define PSU_STATUS_I2C_REG_OFFSET 0x03 + +#define IS_POWER_GOOD(id, value) (!!(value & BIT(id + 1))) + +static ssize_t show_count(struct device *dev, struct device_attribute *da, char *buf); +static ssize_t show_status(struct device *dev, struct device_attribute *da, char *buf); +extern int as4224_cpld_read(unsigned short cpld_addr, u8 reg); +extern int as4224_cpld_write(unsigned short cpld_addr, u8 reg, u8 value); + +enum Platform_Id { + AS5114_48X, + AS4224_52P, + AS4224_52T, + AS4224_52T_DAC +}; + +/* Each client has this additional data + */ +struct as4224_psu_data { + struct platform_device *pdev; + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* !=0 if registers are valid */ + unsigned long last_updated; /* In jiffies */ + enum Platform_Id platform_id; + u8 status; /* power_good status read from CPLD */ + u8 psu_count; +}; + +struct as4224_psu_data *data = NULL; + +enum as4224_psu_sysfs_attributes { + PSU1_POWER_GOOD, + PSU2_POWER_GOOD, + PSU_COUNT +}; + +/* sysfs attributes for hwmon + */ +static SENSOR_DEVICE_ATTR(psu_count, S_IRUGO, show_count, NULL, PSU_COUNT); + +static struct attribute *psu_attributes_common[] = { + &sensor_dev_attr_psu_count.dev_attr.attr, + NULL +}; + +#define PSU_ATTRS_COMMON() { .attrs = psu_attributes_common } + +#define PSU_ATTRS(pid) \ + static struct sensor_device_attribute sensor_attr_psu##pid = \ + SENSOR_ATTR(psu##pid##_power_good, S_IRUGO, show_status, NULL, PSU##pid##_POWER_GOOD); \ + static struct attribute *psu_attributes##pid[] = { \ + &sensor_attr_psu##pid.dev_attr.attr, \ + NULL \ + } + +PSU_ATTRS(1); +PSU_ATTRS(2); + +#define PSU_ATTR_GROUP(pid) { .attrs = psu_attributes##pid } + +static struct attribute_group psu_group[] = { + PSU_ATTRS_COMMON(), + PSU_ATTR_GROUP(1), + PSU_ATTR_GROUP(2), +}; + +static int as4224_psu_read_value(u8 reg) +{ + return as4224_cpld_read(PSU_STATUS_I2C_ADDR, reg); +} + +static int as4224_psu_write_value(u8 reg, u8 value) +{ + return as4224_cpld_write(PSU_STATUS_I2C_ADDR, reg, value); +} + +static int as4224_psu_get_status_mask_reg(void) +{ + return (data->platform_id == AS5114_48X) ? 0x32 : 0x34; +} + +static struct as4224_psu_data *as4224_psu_update_device(struct device *dev) +{ + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int status; + int reg; + int reg_mask = 0x6; + + dev_dbg(dev, "Starting as4224 update\n"); + data->valid = 0; + + /* Read psu status */ + reg = as4224_psu_get_status_mask_reg(); + status = as4224_psu_read_value(reg); + if (unlikely(status < 0)) { + dev_dbg(dev, "cpld read reg (0x%x) err %d\n", reg, status); + return data; + } + + /* Enable the interrupt to CPU */ + status = as4224_psu_write_value(reg, status & (~reg_mask)); + if (unlikely(status < 0)) { + dev_dbg(dev, "cpld write reg (0x%x) err %d\n", reg, status); + return data; + } + + status = as4224_psu_read_value(PSU_STATUS_I2C_REG_OFFSET); + + if (status < 0) { + dev_dbg(dev, "cpld read reg (0x%x) err %d\n", PSU_STATUS_I2C_REG_OFFSET, status); + return data; + } + else { + data->status = status; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + return data; +} + +static ssize_t show_count(struct device *dev, struct device_attribute *da, char *buf) +{ + return sprintf(buf, "%d\n", data->psu_count); +} + +static ssize_t show_status(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + u8 status = 0; + + mutex_lock(&data->update_lock); + + data = as4224_psu_update_device(dev); + if (!data->valid) { + mutex_unlock(&data->update_lock); + return sprintf(buf, "0\n"); + } + + status = IS_POWER_GOOD(attr->index, data->status); + + mutex_unlock(&data->update_lock); + return sprintf(buf, "%d\n", status); +} + +static int as4224_psu_get_psu_count(void) +{ + int status; + + status = as4224_psu_read_value(BOARD_INFO_REG_OFFSET); + if (status < 0) { + return 0; + } + + return (status & 0x10) ? 1 : 2; +} + +static int get_platform_id(void) +{ + int status; + + status = as4224_psu_read_value(BOARD_INFO_REG_OFFSET); + if (status < 0) + return status; + + status &= 0xF0; + + if (status & 0x10) + return AS4224_52T; + else if (status & 0x20) + return AS4224_52T_DAC; + else if (status & 0x80) + return AS5114_48X; + else if (status & 0x40) + return -ENODEV; + else + return AS4224_52P; +} + +static int as4224_psu_probe(struct platform_device *pdev) +{ + int status = -1; + int i = 0; + + /* Register sysfs hooks */ + for (i = 0; i < (data->psu_count + 1); i++) { + /* Register sysfs hooks */ + status = sysfs_create_group(&pdev->dev.kobj, &psu_group[i]); + if (status) { + goto exit; + } + } + + dev_info(&pdev->dev, "device created\n"); + + return 0; + +exit: + for (--i; i >= 0; i--) { + sysfs_remove_group(&pdev->dev.kobj, &psu_group[i]); + } + return status; +} + + +static int as4224_psu_remove(struct platform_device *pdev) +{ + int i = 0; + + for (i = 0; i < (data->psu_count + 1); i++) { + sysfs_remove_group(&pdev->dev.kobj, &psu_group[i]); + } + + return 0; +} + +static struct platform_driver as4224_psu_driver = { + .probe = as4224_psu_probe, + .remove = as4224_psu_remove, + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init as4224_psu_init(void) +{ + int ret; + + data = kzalloc(sizeof(struct as4224_psu_data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto alloc_err; + } + + mutex_init(&data->update_lock); + + data->psu_count = as4224_psu_get_psu_count(); + if (!data->psu_count) { + return -EIO; + } + + ret = get_platform_id(); + if (ret < 0) + return ret; + + data->platform_id = ret; + + ret = platform_driver_register(&as4224_psu_driver); + if (ret < 0) { + goto dri_reg_err; + } + + data->pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(data->pdev)) { + ret = PTR_ERR(data->pdev); + goto dev_reg_err; + } + + return 0; + +dev_reg_err: + platform_driver_unregister(&as4224_psu_driver); +dri_reg_err: + kfree(data); +alloc_err: + return ret; +} + +static void __exit as4224_psu_exit(void) +{ + platform_device_unregister(data->pdev); + platform_driver_unregister(&as4224_psu_driver); + kfree(data); +} + +module_init(as4224_psu_init); +module_exit(as4224_psu_exit); + +MODULE_AUTHOR("Brandon Chuang "); +MODULE_DESCRIPTION("as4224_psu driver"); +MODULE_LICENSE("GPL"); \ No newline at end of file diff --git a/packages/platforms/accton/arm64/as4224/src/modules-5_6/optoe.c b/packages/platforms/accton/arm64/as4224/src/modules-5_6/optoe.c new file mode 100644 index 000000000..f682c574c --- /dev/null +++ b/packages/platforms/accton/arm64/as4224/src/modules-5_6/optoe.c @@ -0,0 +1,1148 @@ +/* + * optoe.c - A driver to read and write the EEPROM on optical transceivers + * (SFP, QSFP and similar I2C based devices) + * + * Copyright (C) 2014 Cumulus networks Inc. + * Copyright (C) 2017 Finisar 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 Freeoftware Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * Description: + * a) Optical transceiver EEPROM read/write transactions are just like + * the at24 eeproms managed by the at24.c i2c driver + * b) The register/memory layout is up to 256 128 byte pages defined by + * a "pages valid" register and switched via a "page select" + * register as explained in below diagram. + * c) 256 bytes are mapped at a time. 'Lower page 00h' is the first 128 + * bytes of address space, and always references the same + * location, independent of the page select register. + * All mapped pages are mapped into the upper 128 bytes + * (offset 128-255) of the i2c address. + * d) Devices with one I2C address (eg QSFP) use I2C address 0x50 + * (A0h in the spec), and map all pages in the upper 128 bytes + * of that address. + * e) Devices with two I2C addresses (eg SFP) have 256 bytes of data + * at I2C address 0x50, and 256 bytes of data at I2C address + * 0x51 (A2h in the spec). Page selection and paged access + * only apply to this second I2C address (0x51). + * e) The address space is presented, by the driver, as a linear + * address space. For devices with one I2C client at address + * 0x50 (eg QSFP), offset 0-127 are in the lower + * half of address 50/A0h/client[0]. Offset 128-255 are in + * page 0, 256-383 are page 1, etc. More generally, offset + * 'n' resides in page (n/128)-1. ('page -1' is the lower + * half, offset 0-127). + * f) For devices with two I2C clients at address 0x50 and 0x51 (eg SFP), + * the address space places offset 0-127 in the lower + * half of 50/A0/client[0], offset 128-255 in the upper + * half. Offset 256-383 is in the lower half of 51/A2/client[1]. + * Offset 384-511 is in page 0, in the upper half of 51/A2/... + * Offset 512-639 is in page 1, in the upper half of 51/A2/... + * Offset 'n' is in page (n/128)-3 (for n > 383) + * + * One I2c addressed (eg QSFP) Memory Map + * + * 2-Wire Serial Address: 1010000x + * + * Lower Page 00h (128 bytes) + * ===================== + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * |Page Select Byte(127)| + * ===================== + * | + * | + * | + * | + * V + * ------------------------------------------------------------ + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * V V V V + * ------------ -------------- --------------- -------------- + * | | | | | | | | + * | Upper | | Upper | | Upper | | Upper | + * | Page 00h | | Page 01h | | Page 02h | | Page 03h | + * | | | (Optional) | | (Optional) | | (Optional | + * | | | | | | | for Cable | + * | | | | | | | Assemblies) | + * | ID | | AST | | User | | | + * | Fields | | Table | | EEPROM Data | | | + * | | | | | | | | + * | | | | | | | | + * | | | | | | | | + * ------------ -------------- --------------- -------------- + * + * The SFF 8436 (QSFP) spec only defines the 4 pages described above. + * In anticipation of future applications and devices, this driver + * supports access to the full architected range, 256 pages. + * + **/ + +/* #define DEBUG 1 */ + +#undef EEPROM_CLASS +#ifdef CONFIG_EEPROM_CLASS +#define EEPROM_CLASS +#endif +#ifdef CONFIG_EEPROM_CLASS_MODULE +#define EEPROM_CLASS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EEPROM_CLASS +#include +#endif + +#include + +/* The maximum length of a port name */ +#define MAX_PORT_NAME_LEN 20 + +struct optoe_platform_data { + u32 byte_len; /* size (sum of all addr) */ + u16 page_size; /* for writes */ + u8 flags; + void *dummy1; /* backward compatibility */ + void *dummy2; /* backward compatibility */ + +#ifdef EEPROM_CLASS + struct eeprom_platform_data *eeprom_data; +#endif + char port_name[MAX_PORT_NAME_LEN]; +}; + +/* fundamental unit of addressing for EEPROM */ +#define OPTOE_PAGE_SIZE 128 +/* + * Single address devices (eg QSFP) have 256 pages, plus the unpaged + * low 128 bytes. If the device does not support paging, it is + * only 2 'pages' long. + */ +#define OPTOE_ARCH_PAGES 256 +#define ONE_ADDR_EEPROM_SIZE ((1 + OPTOE_ARCH_PAGES) * OPTOE_PAGE_SIZE) +#define ONE_ADDR_EEPROM_UNPAGED_SIZE (2 * OPTOE_PAGE_SIZE) +/* + * Dual address devices (eg SFP) have 256 pages, plus the unpaged + * low 128 bytes, plus 256 bytes at 0x50. If the device does not + * support paging, it is 4 'pages' long. + */ +#define TWO_ADDR_EEPROM_SIZE ((3 + OPTOE_ARCH_PAGES) * OPTOE_PAGE_SIZE) +#define TWO_ADDR_EEPROM_UNPAGED_SIZE (4 * OPTOE_PAGE_SIZE) +#define TWO_ADDR_NO_0X51_SIZE (2 * OPTOE_PAGE_SIZE) + +/* a few constants to find our way around the EEPROM */ +#define OPTOE_PAGE_SELECT_REG 0x7F +#define ONE_ADDR_PAGEABLE_REG 0x02 +#define ONE_ADDR_NOT_PAGEABLE (1<<2) +#define TWO_ADDR_PAGEABLE_REG 0x40 +#define TWO_ADDR_PAGEABLE (1<<4) +#define TWO_ADDR_0X51_REG 92 +#define TWO_ADDR_0X51_SUPP (1<<6) +#define OPTOE_ID_REG 0 +#define OPTOE_READ_OP 0 +#define OPTOE_WRITE_OP 1 +#define OPTOE_EOF 0 /* used for access beyond end of device */ + +struct optoe_data { + struct optoe_platform_data chip; + int use_smbus; + char port_name[MAX_PORT_NAME_LEN]; + + /* + * Lock protects against activities from other Linux tasks, + * but not from changes by other I2C masters. + */ + struct mutex lock; + struct bin_attribute bin; + struct attribute_group attr_group; + + u8 *writebuf; + unsigned int write_max; + + unsigned int num_addresses; + +#ifdef EEPROM_CLASS + struct eeprom_device *eeprom_dev; +#endif + + /* dev_class: ONE_ADDR (QSFP) or TWO_ADDR (SFP) */ + int dev_class; + + struct i2c_client *client[]; +}; + + +/* + * This parameter is to help this driver avoid blocking other drivers out + * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C + * clock, one 256 byte read takes about 1/43 second which is excessive; + * but the 1/170 second it takes at 400 kHz may be quite reasonable; and + * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible. + * + * This value is forced to be a power of two so that writes align on pages. + */ +static unsigned int io_limit = OPTOE_PAGE_SIZE; + +/* + * specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +static unsigned int write_timeout = 25; + +/* + * flags to distinguish one-address (QSFP family) from two-address (SFP family) + * If the family is not known, figure it out when the device is accessed + */ +#define ONE_ADDR 1 +#define TWO_ADDR 2 + +static const struct i2c_device_id optoe_ids[] = { + { "optoe1", ONE_ADDR }, + { "optoe2", TWO_ADDR }, + { "sff8436", ONE_ADDR }, + { "24c04", TWO_ADDR }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, optoe_ids); + +/*-------------------------------------------------------------------------*/ +/* + * This routine computes the addressing information to be used for + * a given r/w request. + * + * Task is to calculate the client (0 = i2c addr 50, 1 = i2c addr 51), + * the page, and the offset. + * + * Handles both single address (eg QSFP) and two address (eg SFP). + * For SFP, offset 0-255 are on client[0], >255 is on client[1] + * Offset 256-383 are on the lower half of client[1] + * Pages are accessible on the upper half of client[1]. + * Offset >383 are in 128 byte pages mapped into the upper half + * + * For QSFP, all offsets are on client[0] + * offset 0-127 are on the lower half of client[0] (no paging) + * Pages are accessible on the upper half of client[1]. + * Offset >127 are in 128 byte pages mapped into the upper half + * + * Callers must not read/write beyond the end of a client or a page + * without recomputing the client/page. Hence offset (within page) + * plus length must be less than or equal to 128. (Note that this + * routine does not have access to the length of the call, hence + * cannot do the validity check.) + * + * Offset within Lower Page 00h and Upper Page 00h are not recomputed + */ + +static uint8_t optoe_translate_offset(struct optoe_data *optoe, + loff_t *offset, struct i2c_client **client) +{ + unsigned int page = 0; + + *client = optoe->client[0]; + + /* if SFP style, offset > 255, shift to i2c addr 0x51 */ + if (optoe->dev_class == TWO_ADDR) { + if (*offset > 255) { + /* like QSFP, but shifted to client[1] */ + *client = optoe->client[1]; + *offset -= 256; + } + } + + /* + * if offset is in the range 0-128... + * page doesn't matter (using lower half), return 0. + * offset is already correct (don't add 128 to get to paged area) + */ + if (*offset < OPTOE_PAGE_SIZE) + return page; + + /* note, page will always be positive since *offset >= 128 */ + page = (*offset >> 7)-1; + /* 0x80 places the offset in the top half, offset is last 7 bits */ + *offset = OPTOE_PAGE_SIZE + (*offset & 0x7f); + + return page; /* note also returning client and offset */ +} + +static ssize_t optoe_eeprom_read(struct optoe_data *optoe, + struct i2c_client *client, + char *buf, unsigned int offset, size_t count) +{ + struct i2c_msg msg[2]; + u8 msgbuf[2]; + unsigned long timeout, read_time; + int status, i; + + memset(msg, 0, sizeof(msg)); + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + /*smaller eeproms can work given some SMBus extension calls */ + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + break; + case I2C_SMBUS_WORD_DATA: + /* Check for odd length transaction */ + count = (count == 1) ? 1 : 2; + break; + case I2C_SMBUS_BYTE_DATA: + count = 1; + break; + default: + /* + * When we have a better choice than SMBus calls, use a + * combined I2C message. Write address; then read up to + * io_limit data bytes. msgbuf is u8 and will cast to our + * needs. + */ + i = 0; + msgbuf[i++] = offset; + + msg[0].addr = client->addr; + msg[0].buf = msgbuf; + msg[0].len = i; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = buf; + msg[1].len = count; + } + + /* + * Reads fail if the previous write didn't complete yet. We may + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + read_time = jiffies; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + status = i2c_smbus_read_i2c_block_data(client, offset, + count, buf); + break; + case I2C_SMBUS_WORD_DATA: + status = i2c_smbus_read_word_data(client, offset); + if (status >= 0) { + buf[0] = status & 0xff; + if (count == 2) + buf[1] = status >> 8; + status = count; + } + break; + case I2C_SMBUS_BYTE_DATA: + status = i2c_smbus_read_byte_data(client, offset); + if (status >= 0) { + buf[0] = status; + status = count; + } + break; + default: + status = i2c_transfer(client->adapter, msg, 2); + if (status == 2) + status = count; + } + + dev_dbg(&client->dev, "eeprom read %zu@%d --> %d (%ld)\n", + count, offset, status, jiffies); + + if (status == count) /* happy path */ + return count; + + if (status == -ENXIO) /* no module present */ + return status; + + /* REVISIT: at HZ=100, this is sloooow */ + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return -ETIMEDOUT; +} + +static ssize_t optoe_eeprom_write(struct optoe_data *optoe, + struct i2c_client *client, + const char *buf, + unsigned int offset, size_t count) +{ + struct i2c_msg msg; + ssize_t status; + unsigned long timeout, write_time; + unsigned int next_page_start; + int i = 0; + + /* write max is at most a page + * (In this driver, write_max is actually one byte!) + */ + if (count > optoe->write_max) + count = optoe->write_max; + + /* shorten count if necessary to avoid crossing page boundary */ + next_page_start = roundup(offset + 1, OPTOE_PAGE_SIZE); + if (offset + count > next_page_start) + count = next_page_start - offset; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + /*smaller eeproms can work given some SMBus extension calls */ + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + break; + case I2C_SMBUS_WORD_DATA: + /* Check for odd length transaction */ + count = (count == 1) ? 1 : 2; + break; + case I2C_SMBUS_BYTE_DATA: + count = 1; + break; + default: + /* If we'll use I2C calls for I/O, set up the message */ + msg.addr = client->addr; + msg.flags = 0; + + /* msg.buf is u8 and casts will mask the values */ + msg.buf = optoe->writebuf; + + msg.buf[i++] = offset; + memcpy(&msg.buf[i], buf, count); + msg.len = i + count; + break; + } + + /* + * Reads fail if the previous write didn't complete yet. We may + * loop a few times until this one succeeds, waiting at least + * long enough for one entire page write to work. + */ + timeout = jiffies + msecs_to_jiffies(write_timeout); + do { + write_time = jiffies; + + switch (optoe->use_smbus) { + case I2C_SMBUS_I2C_BLOCK_DATA: + status = i2c_smbus_write_i2c_block_data(client, + offset, count, buf); + if (status == 0) + status = count; + break; + case I2C_SMBUS_WORD_DATA: + if (count == 2) { + status = i2c_smbus_write_word_data(client, + offset, (u16)((buf[0])|(buf[1] << 8))); + } else { + /* count = 1 */ + status = i2c_smbus_write_byte_data(client, + offset, buf[0]); + } + if (status == 0) + status = count; + break; + case I2C_SMBUS_BYTE_DATA: + status = i2c_smbus_write_byte_data(client, offset, + buf[0]); + if (status == 0) + status = count; + break; + default: + status = i2c_transfer(client->adapter, &msg, 1); + if (status == 1) + status = count; + break; + } + + dev_dbg(&client->dev, "eeprom write %zu@%d --> %ld (%lu)\n", + count, offset, (long int) status, jiffies); + + if (status == count) + return count; + + /* REVISIT: at HZ=100, this is sloooow */ + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return -ETIMEDOUT; +} + + +static ssize_t optoe_eeprom_update_client(struct optoe_data *optoe, + char *buf, loff_t off, + size_t count, int opcode) +{ + struct i2c_client *client; + ssize_t retval = 0; + uint8_t page = 0; + loff_t phy_offset = off; + int ret = 0; + + page = optoe_translate_offset(optoe, &phy_offset, &client); + dev_dbg(&client->dev, + "%s off %lld page:%d phy_offset:%lld, count:%ld, opcode:%d\n", + __func__, off, page, phy_offset, (long int) count, opcode); + if (page > 0) { + ret = optoe_eeprom_write(optoe, client, &page, + OPTOE_PAGE_SELECT_REG, 1); + if (ret < 0) { + dev_dbg(&client->dev, + "Write page register for page %d failed ret:%d!\n", + page, ret); + return ret; + } + } + + while (count) { + ssize_t status; + + if (opcode == OPTOE_READ_OP) { + status = optoe_eeprom_read(optoe, client, + buf, phy_offset, count); + } else { + status = optoe_eeprom_write(optoe, client, + buf, phy_offset, count); + } + if (status <= 0) { + if (retval == 0) + retval = status; + break; + } + buf += status; + phy_offset += status; + count -= status; + retval += status; + } + + + if (page > 0) { + /* return the page register to page 0 (why?) */ + page = 0; + ret = optoe_eeprom_write(optoe, client, &page, + OPTOE_PAGE_SELECT_REG, 1); + if (ret < 0) { + dev_err(&client->dev, + "Restore page register to 0 failed:%d!\n", ret); + /* error only if nothing has been transferred */ + if (retval == 0) + retval = ret; + } + } + return retval; +} + +/* + * Figure out if this access is within the range of supported pages. + * Note this is called on every access because we don't know if the + * module has been replaced since the last call. + * If/when modules support more pages, this is the routine to update + * to validate and allow access to additional pages. + * + * Returns updated len for this access: + * - entire access is legal, original len is returned. + * - access begins legal but is too long, len is truncated to fit. + * - initial offset exceeds supported pages, return OPTOE_EOF (zero) + */ +static ssize_t optoe_page_legal(struct optoe_data *optoe, + loff_t off, size_t len) +{ + struct i2c_client *client = optoe->client[0]; + u8 regval; + int status; + size_t maxlen; + + if (off < 0) + return -EINVAL; + if (optoe->dev_class == TWO_ADDR) { + /* SFP case */ + /* if only using addr 0x50 (first 256 bytes) we're good */ + if ((off + len) <= TWO_ADDR_NO_0X51_SIZE) + return len; + /* if offset exceeds possible pages, we're not good */ + if (off >= TWO_ADDR_EEPROM_SIZE) + return OPTOE_EOF; + /* in between, are pages supported? */ + status = optoe_eeprom_read(optoe, client, ®val, + TWO_ADDR_PAGEABLE_REG, 1); + if (status < 0) + return status; /* error out (no module?) */ + if (regval & TWO_ADDR_PAGEABLE) { + /* Pages supported, trim len to the end of pages */ + maxlen = TWO_ADDR_EEPROM_SIZE - off; + } else { + /* pages not supported, trim len to unpaged size */ + if (off >= TWO_ADDR_EEPROM_UNPAGED_SIZE) + return OPTOE_EOF; + + /* will be accessing addr 0x51, is that supported? */ + /* byte 92, bit 6 implies DDM support, 0x51 support */ + status = optoe_eeprom_read(optoe, client, ®val, + TWO_ADDR_0X51_REG, 1); + if (status < 0) + return status; + if (regval & TWO_ADDR_0X51_SUPP) { + /* addr 0x51 is OK */ + maxlen = TWO_ADDR_EEPROM_UNPAGED_SIZE - off; + } else { + /* addr 0x51 NOT supported, trim to 256 max */ + if (off >= TWO_ADDR_NO_0X51_SIZE) + return OPTOE_EOF; + maxlen = TWO_ADDR_NO_0X51_SIZE - off; + } + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, + "page_legal, SFP, off %lld len %ld\n", + off, (long int) len); + } else { + /* QSFP case */ + /* if no pages needed, we're good */ + if ((off + len) <= ONE_ADDR_EEPROM_UNPAGED_SIZE) + return len; + /* if offset exceeds possible pages, we're not good */ + if (off >= ONE_ADDR_EEPROM_SIZE) + return OPTOE_EOF; + /* in between, are pages supported? */ + status = optoe_eeprom_read(optoe, client, ®val, + ONE_ADDR_PAGEABLE_REG, 1); + if (status < 0) + return status; /* error out (no module?) */ + if (regval & ONE_ADDR_NOT_PAGEABLE) { + /* pages not supported, trim len to unpaged size */ + if (off >= ONE_ADDR_EEPROM_UNPAGED_SIZE) + return OPTOE_EOF; + maxlen = ONE_ADDR_EEPROM_UNPAGED_SIZE - off; + } else { + /* Pages supported, trim len to the end of pages */ + maxlen = ONE_ADDR_EEPROM_SIZE - off; + } + len = (len > maxlen) ? maxlen : len; + dev_dbg(&client->dev, + "page_legal, QSFP, off %lld len %ld\n", + off, (long int) len); + } + return len; +} + +static ssize_t optoe_read_write(struct optoe_data *optoe, + char *buf, loff_t off, size_t len, int opcode) +{ + struct i2c_client *client = optoe->client[0]; + int chunk; + int status = 0; + ssize_t retval; + size_t pending_len = 0, chunk_len = 0; + loff_t chunk_offset = 0, chunk_start_offset = 0; + loff_t chunk_end_offset = 0; + + dev_dbg(&client->dev, + "%s: off %lld len:%ld, opcode:%s\n", + __func__, off, (long int) len, + (opcode == OPTOE_READ_OP) ? "r" : "w"); + if (unlikely(!len)) + return len; + + /* + * Read data from chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&optoe->lock); + + /* + * Confirm this access fits within the device suppored addr range + */ + status = optoe_page_legal(optoe, off, len); + if ((status == OPTOE_EOF) || (status < 0)) { + mutex_unlock(&optoe->lock); + return status; + } + len = status; + + /* + * For each (128 byte) chunk involved in this request, issue a + * separate call to sff_eeprom_update_client(), to + * ensure that each access recalculates the client/page + * and writes the page register as needed. + * Note that chunk to page mapping is confusing, is different for + * QSFP and SFP, and never needs to be done. Don't try! + */ + pending_len = len; /* amount remaining to transfer */ + retval = 0; /* amount transferred */ + for (chunk = off >> 7; chunk <= (off + len - 1) >> 7; chunk++) { + + /* + * Compute the offset and number of bytes to be read/write + * + * 1. start at an offset not equal to 0 (within the chunk) + * and read/write less than the rest of the chunk + * 2. start at an offset not equal to 0 and read/write the rest + * of the chunk + * 3. start at offset 0 (within the chunk) and read/write less + * than entire chunk + * 4. start at offset 0 (within the chunk), and read/write + * the entire chunk + */ + chunk_start_offset = chunk * OPTOE_PAGE_SIZE; + chunk_end_offset = chunk_start_offset + OPTOE_PAGE_SIZE; + + if (chunk_start_offset < off) { + chunk_offset = off; + if ((off + pending_len) < chunk_end_offset) + chunk_len = pending_len; + else + chunk_len = chunk_end_offset - off; + } else { + chunk_offset = chunk_start_offset; + if (pending_len < OPTOE_PAGE_SIZE) + chunk_len = pending_len; + else + chunk_len = OPTOE_PAGE_SIZE; + } + + dev_dbg(&client->dev, + "sff_r/w: off %lld, len %ld, chunk_start_offset %lld, chunk_offset %lld, chunk_len %ld, pending_len %ld\n", + off, (long int) len, chunk_start_offset, chunk_offset, + (long int) chunk_len, (long int) pending_len); + + /* + * note: chunk_offset is from the start of the EEPROM, + * not the start of the chunk + */ + status = optoe_eeprom_update_client(optoe, buf, + chunk_offset, chunk_len, opcode); + if (status != chunk_len) { + /* This is another 'no device present' path */ + dev_dbg(&client->dev, + "o_u_c: chunk %d c_offset %lld c_len %ld failed %d!\n", + chunk, chunk_offset, (long int) chunk_len, status); + if (status > 0) + retval += status; + if (retval == 0) + retval = status; + break; + } + buf += status; + pending_len -= status; + retval += status; + } + mutex_unlock(&optoe->lock); + + return retval; +} + +static ssize_t optoe_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(container_of(kobj, + struct device, kobj)); + struct optoe_data *optoe = i2c_get_clientdata(client); + + return optoe_read_write(optoe, buf, off, count, OPTOE_READ_OP); +} + + +static ssize_t optoe_bin_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = to_i2c_client(container_of(kobj, + struct device, kobj)); + struct optoe_data *optoe = i2c_get_clientdata(client); + + return optoe_read_write(optoe, buf, off, count, OPTOE_WRITE_OP); +} + +static int optoe_remove(struct i2c_client *client) +{ + struct optoe_data *optoe; + int i; + + optoe = i2c_get_clientdata(client); + sysfs_remove_group(&client->dev.kobj, &optoe->attr_group); + sysfs_remove_bin_file(&client->dev.kobj, &optoe->bin); + + for (i = 1; i < optoe->num_addresses; i++) + i2c_unregister_device(optoe->client[i]); + +#ifdef EEPROM_CLASS + eeprom_device_unregister(optoe->eeprom_dev); +#endif + + kfree(optoe->writebuf); + kfree(optoe); + return 0; +} + +static ssize_t show_dev_class(struct device *dev, + struct device_attribute *dattr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + ssize_t count; + + mutex_lock(&optoe->lock); + count = sprintf(buf, "%d\n", optoe->dev_class); + mutex_unlock(&optoe->lock); + + return count; +} + +static ssize_t set_dev_class(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + int dev_class; + + /* + * dev_class is actually the number of i2c addresses used, thus + * legal values are "1" (QSFP class) and "2" (SFP class) + */ + + if (kstrtoint(buf, 0, &dev_class) != 0 || + dev_class < 1 || dev_class > 2) + return -EINVAL; + + mutex_lock(&optoe->lock); + optoe->dev_class = dev_class; + mutex_unlock(&optoe->lock); + + return count; +} + +/* + * if using the EEPROM CLASS driver, we don't report a port_name, + * the EEPROM CLASS drive handles that. Hence all this code is + * only compiled if we are NOT using the EEPROM CLASS driver. + */ +#ifndef EEPROM_CLASS + +static ssize_t show_port_name(struct device *dev, + struct device_attribute *dattr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + ssize_t count; + + mutex_lock(&optoe->lock); + count = sprintf(buf, "%s\n", optoe->port_name); + mutex_unlock(&optoe->lock); + + return count; +} + +static ssize_t set_port_name(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct optoe_data *optoe = i2c_get_clientdata(client); + char port_name[MAX_PORT_NAME_LEN]; + + /* no checking, this value is not used except by show_port_name */ + + if (sscanf(buf, "%19s", port_name) != 1) + return -EINVAL; + + mutex_lock(&optoe->lock); + strcpy(optoe->port_name, port_name); + mutex_unlock(&optoe->lock); + + return count; +} + +static DEVICE_ATTR(port_name, 0644, show_port_name, set_port_name); +#endif /* if NOT defined EEPROM_CLASS, the common case */ + +static DEVICE_ATTR(dev_class, 0644, show_dev_class, set_dev_class); + +static struct attribute *optoe_attrs[] = { +#ifndef EEPROM_CLASS + &dev_attr_port_name.attr, +#endif + &dev_attr_dev_class.attr, + NULL, +}; + +static struct attribute_group optoe_attr_group = { + .attrs = optoe_attrs, +}; + +static int optoe_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + int use_smbus = 0; + struct optoe_platform_data chip; + struct optoe_data *optoe; + int num_addresses = 0; + char port_name[MAX_PORT_NAME_LEN]; + + if (client->addr != 0x50) { + dev_dbg(&client->dev, "probe, bad i2c addr: 0x%x\n", + client->addr); + err = -EINVAL; + goto exit; + } + + if (client->dev.platform_data) { + chip = *(struct optoe_platform_data *)client->dev.platform_data; + /* take the port name from the supplied platform data */ +#ifdef EEPROM_CLASS + strncpy(port_name, chip.eeprom_data->label, MAX_PORT_NAME_LEN); +#else + memcpy(port_name, chip.port_name, MAX_PORT_NAME_LEN); +#endif + dev_dbg(&client->dev, + "probe, chip provided, flags:0x%x; name: %s\n", + chip.flags, client->name); + } else { + if (!id->driver_data) { + err = -ENODEV; + goto exit; + } + dev_dbg(&client->dev, "probe, building chip\n"); + strcpy(port_name, "unitialized"); + chip.flags = 0; +#ifdef EEPROM_CLASS + chip.eeprom_data = NULL; +#endif + } + + /* Use I2C operations unless we're stuck with SMBus extensions. */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + use_smbus = I2C_SMBUS_I2C_BLOCK_DATA; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + use_smbus = I2C_SMBUS_WORD_DATA; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA)) { + use_smbus = I2C_SMBUS_BYTE_DATA; + } else { + err = -EPFNOSUPPORT; + goto exit; + } + } + + + /* + * Make room for two i2c clients + */ + num_addresses = 2; + + optoe = kzalloc(sizeof(struct optoe_data) + + num_addresses * sizeof(struct i2c_client *), + GFP_KERNEL); + if (!optoe) { + err = -ENOMEM; + goto exit; + } + + mutex_init(&optoe->lock); + + /* determine whether this is a one-address or two-address module */ + if ((strcmp(client->name, "optoe1") == 0) || + (strcmp(client->name, "sff8436") == 0)) { + /* one-address (eg QSFP) family */ + optoe->dev_class = ONE_ADDR; + chip.byte_len = ONE_ADDR_EEPROM_SIZE; + num_addresses = 1; + } else if ((strcmp(client->name, "optoe2") == 0) || + (strcmp(client->name, "24c04") == 0)) { + /* SFP family */ + optoe->dev_class = TWO_ADDR; + chip.byte_len = TWO_ADDR_EEPROM_SIZE; + } else { /* those were the only two choices */ + err = -EINVAL; + goto exit; + } + + dev_dbg(&client->dev, "dev_class: %d\n", optoe->dev_class); + optoe->use_smbus = use_smbus; + optoe->chip = chip; + optoe->num_addresses = num_addresses; + memcpy(optoe->port_name, port_name, MAX_PORT_NAME_LEN); + + /* + * Export the EEPROM bytes through sysfs, since that's convenient. + * By default, only root should see the data (maybe passwords etc) + */ + sysfs_bin_attr_init(&optoe->bin); + optoe->bin.attr.name = "eeprom"; + optoe->bin.attr.mode = 0444; + optoe->bin.read = optoe_bin_read; + optoe->bin.size = chip.byte_len; + + if (!use_smbus || + (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_WORD_DATA) || + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + /* + * NOTE: AN-2079 + * Finisar recommends that the host implement 1 byte writes + * only since this module only supports 32 byte page boundaries. + * 2 byte writes are acceptable for PE and Vout changes per + * Application Note AN-2071. + */ + unsigned int write_max = 1; + + optoe->bin.write = optoe_bin_write; + optoe->bin.attr.mode |= 0200; + + if (write_max > io_limit) + write_max = io_limit; + if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) + write_max = I2C_SMBUS_BLOCK_MAX; + optoe->write_max = write_max; + + /* buffer (data + address at the beginning) */ + optoe->writebuf = kmalloc(write_max + 2, GFP_KERNEL); + if (!optoe->writebuf) { + err = -ENOMEM; + goto exit_kfree; + } + } else { + dev_warn(&client->dev, + "cannot write due to controller restrictions."); + } + + optoe->client[0] = client; + + /* SFF-8472 spec requires that the second I2C address be 0x51 */ + if (num_addresses == 2) { + optoe->client[1] = i2c_new_dummy_device(client->adapter, 0x51); + if (IS_ERR(optoe->client[1])) { + dev_err(&client->dev, "address 0x51 unavailable\n"); + err = -EADDRINUSE; + goto err_struct; + } + } + + /* create the sysfs eeprom file */ + err = sysfs_create_bin_file(&client->dev.kobj, &optoe->bin); + if (err) + goto err_struct; + + optoe->attr_group = optoe_attr_group; + + err = sysfs_create_group(&client->dev.kobj, &optoe->attr_group); + if (err) { + dev_err(&client->dev, "failed to create sysfs attribute group.\n"); + goto err_struct; + } + +#ifdef EEPROM_CLASS + optoe->eeprom_dev = eeprom_device_register(&client->dev, + chip.eeprom_data); + if (IS_ERR(optoe->eeprom_dev)) { + dev_err(&client->dev, "error registering eeprom device.\n"); + err = PTR_ERR(optoe->eeprom_dev); + goto err_sysfs_cleanup; + } +#endif + + i2c_set_clientdata(client, optoe); + + dev_info(&client->dev, "%zu byte %s EEPROM, %s\n", + optoe->bin.size, client->name, + optoe->bin.write ? "read/write" : "read-only"); + + if (use_smbus == I2C_SMBUS_WORD_DATA || + use_smbus == I2C_SMBUS_BYTE_DATA) { + dev_notice(&client->dev, + "Falling back to %s reads, performance will suffer\n", + use_smbus == I2C_SMBUS_WORD_DATA ? "word" : "byte"); + } + + return 0; + +#ifdef EEPROM_CLASS +err_sysfs_cleanup: + sysfs_remove_group(&client->dev.kobj, &optoe->attr_group); + sysfs_remove_bin_file(&client->dev.kobj, &optoe->bin); +#endif + +err_struct: + if (num_addresses == 2) { + if (optoe->client[1]) + i2c_unregister_device(optoe->client[1]); + } + + kfree(optoe->writebuf); +exit_kfree: + kfree(optoe); +exit: + dev_dbg(&client->dev, "probe error %d\n", err); + + return err; +} + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver optoe_driver = { + .driver = { + .name = "optoe", + .owner = THIS_MODULE, + }, + .probe = optoe_probe, + .remove = optoe_remove, + .id_table = optoe_ids, +}; + +static int __init optoe_init(void) +{ + + if (!io_limit) { + pr_err("optoe: io_limit must not be 0!\n"); + return -EINVAL; + } + + io_limit = rounddown_pow_of_two(io_limit); + return i2c_add_driver(&optoe_driver); +} +module_init(optoe_init); + +static void __exit optoe_exit(void) +{ + i2c_del_driver(&optoe_driver); +} +module_exit(optoe_exit); + +MODULE_DESCRIPTION("Driver for optical transceiver (SFP, QSFP, ...) EEPROMs"); +MODULE_AUTHOR("DON BOLLINGER "); +MODULE_LICENSE("GPL"); diff --git a/packages/platforms/accton/arm64/as4564-26p/modules/PKG.yml b/packages/platforms/accton/arm64/as4564-26p/modules/PKG.yml index 89434c91c..6ec199687 100644 --- a/packages/platforms/accton/arm64/as4564-26p/modules/PKG.yml +++ b/packages/platforms/accton/arm64/as4564-26p/modules/PKG.yml @@ -1 +1 @@ -!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4564-26p KERNELS="onl-kernel-5.15-lts-arm64-all:arm64" +!include $ONL_TEMPLATES/platform-modules.yml ARCH=arm64 VENDOR=accton BASENAME=arm64-accton-as4564-26p KERNELS="onl-kernel-5.10-lts-arm64-all:arm64" diff --git a/packages/platforms/accton/arm64/as4564-26p/modules/builds/Makefile b/packages/platforms/accton/arm64/as4564-26p/modules/builds/Makefile index d0935d232..f283ffd16 100644 --- a/packages/platforms/accton/arm64/as4564-26p/modules/builds/Makefile +++ b/packages/platforms/accton/arm64/as4564-26p/modules/builds/Makefile @@ -1,4 +1,4 @@ -KERNELS := onl-kernel-5.15-lts-arm64-all:arm64 +KERNELS := onl-kernel-5.10-lts-arm64-all:arm64 KMODULES := src VENDOR := accton BASENAME := arm64-accton-as4564-26p diff --git a/packages/platforms/accton/arm64/as4564-26p/platform-config/r0/src/lib/arm64-accton-as4564-26p-r0.yml b/packages/platforms/accton/arm64/as4564-26p/platform-config/r0/src/lib/arm64-accton-as4564-26p-r0.yml index ca9d2bc63..4f7ea2c6b 100644 --- a/packages/platforms/accton/arm64/as4564-26p/platform-config/r0/src/lib/arm64-accton-as4564-26p-r0.yml +++ b/packages/platforms/accton/arm64/as4564-26p/platform-config/r0/src/lib/arm64-accton-as4564-26p-r0.yml @@ -9,10 +9,10 @@ arm64-accton-as4564-26p-r0: flat_image_tree: kernel: - <<: *arm64-kernel-5-15 + <<: *arm64-kernel-5-10 dtb: =: accton-as4564-26p.dtb - <<: *arm64-kernel-5-15-package + <<: *arm64-kernel-5-10-package itb: <<: *arm64-itb