[PATCH 1/2] staging: jnx: Juniper subsystem & board core APIs

From: Pantelis Antoniou
Date: Fri Oct 07 2016 - 11:19:35 EST


From: Tom Kavanagh <tkavanagh@xxxxxxxxxxx>

The Juniper System Infrastructure subsystem creates platform devices
for the platform, chassis and cards and provides sysfs attributes for
these devices.

Each board contains I2C ID EEPROMs which contain information about the
chassis, platform, type and assembly IDs.

All Juniper local boards that run Linux contain a CBD (Control Board)
FPGA that provides I2C interfaces (among other things) that the other
boards use to connect their ID EEPROMs to.

A set of APIs for boards/modules is provided. These APIs provide:

- The ability to "listen" for the addition/deletion of any new board
and schedule work to be done upon notification.
- The query of mastership in the dual failover environment these
platform operate in.
- Registering boards and chassis.

A user-space API is provided by sysfs device files and it is described
in the relevant ABI documentation file.

Signed-off-by: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
Signed-off-by: Guenter Roeck <groeck@xxxxxxxxxxx>
Signed-off-by: Mohammad Kamil <mkamil@xxxxxxxxxxx>
Signed-off-by: Rajat Jain <rajatjain@xxxxxxxxxxx>
Signed-off-by: Tom Kavanagh <tkavanagh@xxxxxxxxxxx>
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx>
---
Documentation/ABI/testing/sysfs-platform-jnx | 170 +++++++
drivers/staging/Kconfig | 2 +
drivers/staging/Makefile | 1 +
drivers/staging/jnx/Kconfig | 24 +
drivers/staging/jnx/Makefile | 5 +
drivers/staging/jnx/jnx-board-core.c | 247 ++++++++++
drivers/staging/jnx/jnx-subsys-private.h | 35 ++
drivers/staging/jnx/jnx-subsys.c | 655 +++++++++++++++++++++++++++
include/linux/jnx/jnx-board-core.h | 41 ++
include/linux/jnx/jnx-subsys.h | 94 ++++
10 files changed, 1274 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-jnx
create mode 100644 drivers/staging/jnx/Kconfig
create mode 100644 drivers/staging/jnx/Makefile
create mode 100644 drivers/staging/jnx/jnx-board-core.c
create mode 100644 drivers/staging/jnx/jnx-subsys-private.h
create mode 100644 drivers/staging/jnx/jnx-subsys.c
create mode 100644 include/linux/jnx/jnx-board-core.h
create mode 100644 include/linux/jnx/jnx-subsys.h

diff --git a/Documentation/ABI/testing/sysfs-platform-jnx b/Documentation/ABI/testing/sysfs-platform-jnx
new file mode 100644
index 0000000..9bc7adc
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-jnx
@@ -0,0 +1,170 @@
+What: /sys/devices/platform/jnx/chassis/platform
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only numeric platform ID of the Juniper platform we're
+ running on. Valid platforms IDs are:
+ SANGRIA 85
+ HENDRICKS 156
+ POLARIS 171
+ OMEGA 181
+
+What: /sys/devices/platform/jnx/chassis/chassis_no
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only chassis number.
+
+What: /sys/devices/platform/jnx/chassis/multichassis
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only multichassis status
+ 1 if we're on a multichassis system
+ 0 otherwise
+
+What: /sys/devices/platform/jnx/chassis/master
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only numeric ID of the master slot.
+
+What: /sys/devices/platform/jnx/chassis/mastership
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read for returning non zero if current RE is master
+ Write for relinquishing mastership to other RE
+
+What: /sys/devices/platform/jnx/chassis/mastership_alive
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Write to update the mastership watchdog alive.
+ The watchdog period is CBD FPGA specific.
+
+What: /sys/devices/platform/jnx/chassis/mastership_alive_cnt
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read to return the mastership count number
+ Write to set the mastership count number
+
+What: /sys/devices/platform/jnx/card/foo[n]
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Symbolic link of the jnx board with the given type
+ foo with an option index number. For instance the
+ first two fan type cards would link like this:
+
+ /sys/devices/platform/jnx/card/fan0 -> ../jnx-09cc.0
+ /sys/devices/platform/jnx/card/fan1 -> ../jnx-09cc.1
+
+What: /sys/devices/platform/jnx/jnx-XXXX.N/id
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Symbolic link of the jnx board with hexadecimal assembly ID
+ XXXX and platform id index N to it's EEPROM ID device
+
+What: /sys/devices/platform/jnx/jnx-XXXX.N/i2c-adapter
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Symbolic link of the jnx board with hexadecimal assembly ID
+ XXXX and platform id index N to its i2c-adapter that the
+ EEPROM ID device is located
+
+What: /sys/devices/platform/jnx-XXXX.N/slot
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only number slot ID of the jnx board with hexadecimal
+ assembly ID XXXX and platform id index N
+
+What: /sys/devices/platform/jnx-XXXX.N/type
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only board type ID of the jnx board with hexadecimal
+ assembly ID XXXX and platform id index N.
+ Valid board type IDs:
+ 0 Unknown
+ 1 RE
+ 2 FPC
+ 3 SPMB
+ 4 CB
+ 5 PS
+ 6 FAN
+ 7 FPM
+ 8 CG
+ 9 MIDPLANE
+ 10 PIC
+ 11 SIB
+
+What: /sys/devices/platform/jnx-XXXX.N/assembly_id
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only assembly ID of the local jnx board with hexadecimal
+ assembly ID XXXX and platform id index N which is read from
+ the EEPROM
+
+What: /sys/devices/platform/jnx-XXXX-local/slot
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only number slot ID of the jnx local board with hexadecimal
+ assembly ID XXXX.
+
+What: /sys/devices/platform/jnx-XXXX-local/type
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only board type ID of the jnx local board with hexadecimal
+ assembly ID XXXX.
+ Valid board type IDs:
+ 0 Unknown
+ 1 RE
+ 2 FPC
+ 3 SPMB
+ 4 CB
+ 5 PS
+ 6 FAN
+ 7 FPM
+ 8 CG
+ 9 MIDPLANE
+ 10 PIC
+ 11 SIB
+
+What: /sys/devices/platform/jnx-XXXX-local/assembly_id
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only warmboot status of the jnx local board with
+ hexadecimal assembly ID XXXX.
+
+What: /sys/devices/platform/jnx-XXXX-local/warmboot
+Date: Sep 2016
+KernelVersion: 4.8.0-rc7
+Contact: Georgi Vlaev <gvlaev@xxxxxxxxxxx>
+Description:
+ Read only warmboot status of the jnx local board with
+ hexadecimal assembly ID XXXX.
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 58a7b35..474bc68 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -106,4 +106,6 @@ source "drivers/staging/greybus/Kconfig"

source "drivers/staging/vc04_services/Kconfig"

+source "drivers/staging/jnx/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 2fa9745..e6d7be6 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_VT6655) += vt6655/
obj-$(CONFIG_VT6656) += vt6656/
obj-$(CONFIG_VME_BUS) += vme/
obj-$(CONFIG_IIO) += iio/
+obj-$(CONFIG_JNX_DEVICES) += jnx/
obj-$(CONFIG_FB_SM750) += sm750fb/
obj-$(CONFIG_FB_XGI) += xgifb/
obj-$(CONFIG_USB_EMXX) += emxx_udc/
diff --git a/drivers/staging/jnx/Kconfig b/drivers/staging/jnx/Kconfig
new file mode 100644
index 0000000..5d2b207
--- /dev/null
+++ b/drivers/staging/jnx/Kconfig
@@ -0,0 +1,24 @@
+#
+# Juniper devices
+#
+config JNX_DEVICES
+ bool
+ default n
+
+if JNX_DEVICES
+
+menu "Juniper Devices and Infrastructure"
+
+config JNX_SYSTEM
+ bool "Juniper System Infrastructure"
+ default y
+ help
+ This driver adds support for Juniper System Infrastructure. It creates
+ platform devices for the platform, chassis and cards and provides sysfs
+ attributes for these devices.
+
+ This driver can not be compiled as a module.
+
+endmenu
+
+endif # JNX_DEVICES
diff --git a/drivers/staging/jnx/Makefile b/drivers/staging/jnx/Makefile
new file mode 100644
index 0000000..52b8286
--- /dev/null
+++ b/drivers/staging/jnx/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for Juniper devices that really don't fit anywhere else.
+#
+
+obj-$(CONFIG_JNX_SYSTEM) += jnx-subsys.o jnx-board-core.o
diff --git a/drivers/staging/jnx/jnx-board-core.c b/drivers/staging/jnx/jnx-board-core.c
new file mode 100644
index 0000000..218a8b7
--- /dev/null
+++ b/drivers/staging/jnx/jnx-board-core.c
@@ -0,0 +1,247 @@
+/*
+ * Juniper Generic Board APIs
+ *
+ * Copyright (C) 2012, 2013, 2014 Juniper Networks. All rights reserved.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_data/at24.h>
+#include <linux/slab.h>
+#include <linux/jnx/jnx-subsys.h>
+#include <linux/jnx/jnx-board-core.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/jnx-i2cs-core.h>
+
+#include "jnx-subsys-private.h"
+
+#define DRIVER_VERSION "0.01.0"
+#define DRIVER_DESC "Board Generic HW"
+
+static LIST_HEAD(jnx_i2c_notify_list);
+static DEFINE_MUTEX(jnx_i2c_notify_lock);
+
+static int jnx_i2c_adap_name_match(struct device *dev, void *data)
+{
+ struct i2c_adapter *adap = i2c_verify_adapter(dev);
+ char *name = data;
+
+ if (!adap)
+ return false;
+
+ return !strncmp(adap->name, name, strlen(name));
+}
+
+struct i2c_adapter *jnx_i2c_find_adapter(char *name)
+{
+ struct device *dev;
+ struct i2c_adapter *adap;
+
+ dev = bus_find_device(&i2c_bus_type, NULL, name,
+ jnx_i2c_adap_name_match);
+ if (!dev)
+ return NULL;
+
+ adap = i2c_verify_adapter(dev);
+ if (!adap)
+ put_device(dev);
+
+ return adap;
+}
+EXPORT_SYMBOL(jnx_i2c_find_adapter);
+
+static void jnx_board_ideeprom_callback(struct nvmem_device *nvmem,
+ void *context)
+{
+ struct nvmem_device **pnvmem = context;
+
+ *pnvmem = nvmem;
+}
+
+static struct i2c_client *jnx_add_board_ideeprom(struct i2c_adapter *adap,
+ int slot)
+{
+ struct i2c_board_info binfo = { I2C_BOARD_INFO("24c02", 0x51) };
+ struct nvmem_device *nvmem = NULL;
+ struct at24_platform_data adata = {
+ .byte_len = 256,
+ .page_size = 1,
+ .setup = jnx_board_ideeprom_callback,
+ .context = &nvmem,
+ };
+ struct device *dev = &adap->dev;
+ struct jnx_card_info cinfo = {
+ .type = JNX_BOARD_TYPE_UNKNOWN,
+ .adap = adap,
+ .slot = slot,
+ };
+ struct i2c_client *client;
+ unsigned char buf[2];
+ int err;
+
+ binfo.platform_data = &adata;
+
+ client = i2c_new_device(adap, &binfo);
+ if (!client)
+ return client;
+
+ if (!nvmem || nvmem_device_read(nvmem, 4, 2, buf) != 2)
+ goto error;
+
+ cinfo.assembly_id = (buf[0] << 8) | buf[1];
+ err = jnx_register_board(dev, &client->dev, &cinfo, slot);
+ if (err)
+ goto error;
+
+ return client;
+
+error:
+ i2c_unregister_device(client);
+ return NULL;
+}
+
+/*
+ * The i2cs (cpld) mux driver is instantiated through the i2cs mfd driver.
+ * Provide the necessary information to the mfd driver using platform data.
+ */
+static struct mfd_cell i2cs_cells[] = {
+ {
+ .name = "i2c-mux-i2cs",
+ .of_compatible = "jnx,i2c-mux-i2cs",
+ },
+};
+
+static struct jnx_i2cs_platform_data i2cs_pdata = {
+ .cells = i2cs_cells,
+ .ncells = ARRAY_SIZE(i2cs_cells),
+};
+
+static struct i2c_board_info const jnx_i2cs_board_info = {
+ I2C_BOARD_INFO("jnx_i2cs_fpc", 0x54),
+ .platform_data = &i2cs_pdata,
+};
+
+struct i2c_client *jnx_board_inserted(struct i2c_adapter *adap,
+ int slot, bool has_mux)
+{
+ char name[JNX_BRD_I2C_NAME_LEN];
+ struct i2c_client *mux, *client;
+
+ /*
+ * First add (bus selector) mux adapter if needed
+ *
+ * Devices are connected either to the primary mux
+ * (pca9665 adapter controlled via cbd fpga mux),
+ *
+ * -[pca6996]--[cbd mux]
+ * +----+--- eeprom
+ * | +--- other devices
+ * |
+ * +----
+ * ...
+ *
+ * or through the secondary mux (i2c-mux-cpld).
+ * The secondary mux is a virtual single-channel mux; its purpose
+ * is to enable i2c access to the board in question.
+ *
+ * -[pca6996]--[cbd mux]
+ * +----+--- eeprom
+ * | +--- other devices
+ * |
+ * +----[i2c-mux-cpld]--+--- eeprom
+ * | +--- other devices
+ * ...
+ */
+ if (has_mux) {
+ mux = i2c_new_device(adap, &jnx_i2cs_board_info);
+ if (!mux)
+ return NULL;
+ /* Look for mux adapter */
+ snprintf(name, sizeof(name), "i2c-%d-mux (chan_id 0)",
+ i2c_adapter_id(adap));
+ /*
+ * NOTICE:
+ * The following call will fail if the mux or the mfd driver
+ * are not built into the kernel. Accept this limitation
+ * as the code is expected to be replaced with DT based
+ * instantiation.
+ */
+ adap = jnx_i2c_find_adapter(name);
+ if (!adap)
+ return NULL;
+ }
+
+ /* Add ideeprom either directly or behind mux */
+ client = jnx_add_board_ideeprom(adap, slot);
+ /*
+ * jnx_i2c_find_adapter acquires a hold on the returned adapter.
+ * Time to release it.
+ */
+ if (has_mux)
+ put_device(&adap->dev);
+ if (has_mux && !client) {
+ i2c_unregister_device(mux);
+ return NULL;
+ }
+ return has_mux ? mux : client;
+}
+EXPORT_SYMBOL(jnx_board_inserted);
+
+void jnx_board_removed(struct i2c_adapter *adap, struct i2c_client *client)
+{
+ /*
+ * When removing a board, we have to release the platform driver first.
+ * This is necessary because the platform driver will release the i2c
+ * devices connected to it. The 'client' variable may point to the
+ * secondary mux ('i2c-mux-cpld'). If we release it first, it would
+ * release all downstream clients, which would result in a
+ * double-release, since the platform driver would subsequently
+ * try to release the same clients again.
+ * We can not release every client from here since the platform driver
+ * may be unloaded, which would result in no release, and because
+ * the secondary mux does not exist for all boards.
+ */
+ if (adap)
+ jnx_unregister_board(&adap->dev);
+ if (client)
+ i2c_unregister_device(client);
+}
+EXPORT_SYMBOL(jnx_board_removed);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+
+/* Support kexec feature for Juniper boards:
+ * 1. 'warmboot' in the command indicates a warmboot;
+ * 2. jnx_warmboot API is used to check for warmboot.
+ */
+static bool __jnx_warmbooted;
+
+static int __init jnx_warmboot_set(char *str)
+{
+ __jnx_warmbooted = true;
+ return 0;
+}
+
+early_param("warmboot", jnx_warmboot_set);
+
+bool
+jnx_warmboot(void)
+{
+ return __jnx_warmbooted;
+}
+EXPORT_SYMBOL(jnx_warmboot);
diff --git a/drivers/staging/jnx/jnx-subsys-private.h b/drivers/staging/jnx/jnx-subsys-private.h
new file mode 100644
index 0000000..2e83854
--- /dev/null
+++ b/drivers/staging/jnx/jnx-subsys-private.h
@@ -0,0 +1,35 @@
+/*
+ * Juniper Generic APIs for providing chassis and card information
+ * Private API
+ *
+ * Copyright (C) 2012, 2013, 2014 Juniper Networks. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef _JNX_SUBSYS_PRIVATE_H
+#define _JNX_SUBSYS_PRIVATE_H
+
+#include <linux/jnx/jnx-subsys.h>
+
+/* mastership related */
+int register_mastership_notifier(struct notifier_block *nb);
+int unregister_mastership_notifier(struct notifier_block *nb);
+
+/* returns true is running on master */
+bool jnx_is_master(void);
+
+/* register and unregister a non local juniper board */
+int jnx_register_board(struct device *edev, struct device *ideeprom,
+ struct jnx_card_info *cinfo, int id);
+int jnx_unregister_board(struct device *edev);
+
+#endif /* _JNX_SUBSYS_H */
diff --git a/drivers/staging/jnx/jnx-subsys.c b/drivers/staging/jnx/jnx-subsys.c
new file mode 100644
index 0000000..c49a8e6
--- /dev/null
+++ b/drivers/staging/jnx/jnx-subsys.c
@@ -0,0 +1,655 @@
+/*
+ * Juniper Generic APIs for providing chassis and card information
+ *
+ * Copyright (C) 2012, 2013, 2014 Juniper Networks. All rights reserved.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/i2c.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+
+#include <linux/jnx/jnx-subsys.h>
+#include <linux/jnx/jnx-board-core.h>
+
+#include "jnx-subsys-private.h"
+
+#define DRIVER_VERSION "0.01.0"
+#define DRIVER_AUTHOR "Thomas Kavanagh"
+#define DRIVER_DESC "JNX Subsystem"
+
+static struct platform_device *jnx_platform_device;
+static struct platform_device *jnx_local_card_device;
+
+static struct jnx_chassis_info chassis_info;
+
+/*
+ * Linked list to hold info on all inserted boards.
+ */
+struct jnx_board_entry {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct list_head list;
+};
+
+static LIST_HEAD(jnx_board_list);
+static DEFINE_SPINLOCK(jnx_board_list_lock);
+
+/*
+ * Chassis Attributes
+ *
+ * platform - identifies the product upon which we are running
+ * chassis_no - the chassis number, used mainly in multi-chassis systems
+ * multichassis - indicates whether or not this chassis is part of a
+ * multichassis system
+ */
+static ssize_t jnx_show_platform(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", chassis_info.platform);
+}
+
+static ssize_t jnx_show_chassis_no(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", chassis_info.chassis_no);
+}
+
+static ssize_t jnx_show_multichassis(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", chassis_info.multichassis);
+}
+
+/* Determine mastership status */
+bool jnx_is_master(void)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+ bool is_master = true;
+
+ /* mastership_get() can be NULL when connector running on fpc */
+ if (chinfo->mastership_get)
+ is_master = chinfo->mastership_get(chinfo->master_data);
+
+ return is_master;
+}
+EXPORT_SYMBOL(jnx_is_master);
+
+/* mastership status notifier list and register/unregister functions */
+static BLOCKING_NOTIFIER_HEAD(mastership_notifier_list);
+
+int register_mastership_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&mastership_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL(register_mastership_notifier);
+
+int unregister_mastership_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&mastership_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL(unregister_mastership_notifier);
+
+static ssize_t jnx_get_master(struct device *dev, struct device_attribute *da,
+ char *buf)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+
+ return sprintf(buf, "%d\n",
+ chinfo->get_master(chinfo->master_data));
+}
+
+static ssize_t jnx_mastership_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+
+ return sprintf(buf, "%d\n",
+ chinfo->mastership_get(chinfo->master_data));
+}
+
+static ssize_t jnx_mastership_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+ int val, err;
+ bool mstrship_before_set, mstrship_after_set;
+ char object[JNX_BRD_I2C_NAME_LEN + 8];
+ char subobject[24];
+ char arg0[13]; /* New mastership state */
+ char *envp[4];
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+
+ envp[0] = object;
+ envp[1] = subobject;
+ envp[2] = arg0;
+ envp[3] = NULL;
+
+ mstrship_before_set = chinfo->mastership_get(chinfo->master_data);
+ chinfo->mastership_set(chinfo->master_data, val);
+ mstrship_after_set = chinfo->mastership_get(chinfo->master_data);
+
+ /*
+ * Notifier callback should only get called for the valid combinations
+ * once hw switchover has completed successfully. Calling it for the
+ * rest of the combinations is either harmful or redundant.
+ */
+ if (mstrship_before_set != mstrship_after_set) {
+ /* udev notification of mastership change */
+ sprintf(object, "OBJECT=chassis");
+ sprintf(subobject, "SUBOBJECT=mastership");
+ sprintf(arg0, "ARG0=%s", mstrship_after_set ?
+ "master" : "standby");
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+ /* Notifier callback */
+ blocking_notifier_call_chain(&mastership_notifier_list,
+ val, NULL);
+ }
+
+ return count;
+}
+
+static ssize_t jnx_mastership_ping(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+
+ chinfo->mastership_ping(chinfo->master_data);
+
+ return count;
+}
+
+static ssize_t jnx_mastership_alive_cnt_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+
+ return sprintf(buf, "%d\n",
+ chinfo->mastership_count_get(chinfo->master_data));
+}
+
+static ssize_t jnx_mastership_alive_cnt_set(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+ int val, err;
+
+ err = kstrtoint(buf, 0, &val);
+ if (err)
+ return err;
+
+ err = chinfo->mastership_count_set(chinfo->master_data, val);
+ if (err)
+ return err;
+
+ return count;
+}
+
+static DEVICE_ATTR(platform, S_IRUGO, jnx_show_platform, NULL);
+static DEVICE_ATTR(chassis_no, S_IRUGO, jnx_show_chassis_no, NULL);
+static DEVICE_ATTR(multichassis, S_IRUGO, jnx_show_multichassis, NULL);
+static DEVICE_ATTR(master, S_IRUGO, jnx_get_master, NULL);
+static DEVICE_ATTR(mastership, S_IRUGO | S_IWUSR, jnx_mastership_show,
+ jnx_mastership_set);
+static DEVICE_ATTR(mastership_alive, S_IWUSR, NULL, jnx_mastership_ping);
+static DEVICE_ATTR(mastership_alive_cnt, S_IRUGO | S_IWUSR,
+ jnx_mastership_alive_cnt_show, jnx_mastership_alive_cnt_set);
+
+static struct attribute *jnx_chassis_attrs[] = {
+ &dev_attr_platform.attr,
+ &dev_attr_chassis_no.attr,
+ &dev_attr_multichassis.attr,
+ &dev_attr_master.attr, /* 3 */
+ &dev_attr_mastership.attr, /* 4 */
+ &dev_attr_mastership_alive.attr, /* 5 */
+ &dev_attr_mastership_alive_cnt.attr, /* 6 */
+ NULL
+};
+
+static umode_t jnx_chassis_is_visible(struct kobject *kobj,
+ struct attribute *attr, int index)
+{
+ struct jnx_chassis_info *chinfo = &chassis_info;
+
+ if (index == 3 && !chinfo->get_master)
+ return 0;
+ if (index == 4 && (!chinfo->mastership_get || !chinfo->mastership_set))
+ return 0;
+ if (index == 5 && !chinfo->mastership_ping)
+ return 0;
+ if (index == 6 && (!chinfo->mastership_count_get ||
+ !chinfo->mastership_count_set))
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group jnx_chassis_group = {
+ .name = "chassis",
+ .attrs = jnx_chassis_attrs,
+ .is_visible = jnx_chassis_is_visible,
+};
+
+/*
+ * Card attributes
+ *
+ * slot - slot number for the given board
+ * type - what type of board is inserted: RE, FPC, FAN, etc
+ */
+static ssize_t jnx_show_slot(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ struct jnx_card_info *cinfo = dev_get_platdata(dev);
+
+ return sprintf(buf, "%d\n", cinfo->slot);
+}
+
+static ssize_t jnx_show_type(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ struct jnx_card_info *cinfo = dev_get_platdata(dev);
+
+ return sprintf(buf, "%u\n", cinfo->type);
+}
+
+static ssize_t jnx_show_assembly_id(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ struct jnx_card_info *cinfo = dev_get_platdata(dev);
+
+ return sprintf(buf, "0x%04x\n", cinfo->assembly_id);
+}
+
+static ssize_t jnx_show_warmboot(struct device *dev,
+ struct device_attribute *da,
+ char *buf)
+{
+ return sprintf(buf, "%u\n", jnx_warmboot());
+}
+
+static DEVICE_ATTR(slot, S_IRUGO, jnx_show_slot, NULL);
+static DEVICE_ATTR(type, S_IRUGO, jnx_show_type, NULL);
+static DEVICE_ATTR(assembly_id, S_IRUGO, jnx_show_assembly_id, NULL);
+static DEVICE_ATTR(warmboot, S_IRUGO, jnx_show_warmboot, NULL);
+
+/* Card attributes */
+static struct attribute *jnx_card_attrs[] = {
+ &dev_attr_slot.attr,
+ &dev_attr_type.attr,
+ &dev_attr_assembly_id.attr,
+ NULL
+};
+
+static const struct attribute_group jnx_card_group = {
+ .attrs = jnx_card_attrs,
+};
+
+static const struct attribute_group *jnx_card_groups[] = {
+ &jnx_card_group,
+ NULL
+};
+
+/* With addtional card attributes for 'local' */
+static struct attribute *jnx_card_local_attrs[] = {
+ &dev_attr_warmboot.attr,
+ NULL
+};
+
+static const struct attribute_group jnx_local_card_group = {
+ .attrs = jnx_card_local_attrs
+};
+
+static const struct attribute_group *jnx_local_card_groups[] = {
+ &jnx_card_group,
+ &jnx_local_card_group,
+ NULL
+};
+
+static struct attribute *jnx_attrs[] = {
+ NULL
+};
+
+static const struct attribute_group jnx_group = {
+ .name = "card",
+ .attrs = jnx_attrs,
+};
+
+static const struct attribute_group *jnx_groups[] = {
+ &jnx_group,
+ NULL
+};
+
+static int jnx_platform_uevent(const char *dir, const char *obj,
+ const char *subobj, int event)
+{
+ struct kobject *kobj = &jnx_platform_device->dev.kobj;
+ char objpath[64];
+ char object[JNX_BRD_I2C_NAME_LEN + 8];
+ char subobject[20];
+ char *envp[4];
+ const char *devpath;
+ int ret;
+
+ devpath = kobject_get_path(kobj, GFP_KERNEL);
+ if (!devpath)
+ return -ENOENT;
+
+ envp[0] = objpath;
+ envp[1] = object;
+ envp[2] = subobject;
+ envp[3] = NULL;
+
+ if (dir)
+ snprintf(objpath, sizeof(objpath), "OBJPATH=%s/%s", devpath,
+ dir);
+ else
+ snprintf(objpath, sizeof(objpath), "OBJPATH=%s", devpath);
+ snprintf(object, sizeof(object), "OBJECT=%s", obj);
+
+ if (subobj)
+ snprintf(subobject, sizeof(subobject), "SUBOBJECT=%s", subobj);
+ else
+ sprintf(subobject, "SUBOBJECT=");
+
+ ret = kobject_uevent_env(kobj, event, envp);
+ kfree(devpath);
+ return ret;
+}
+
+static int jnx_subsystem_init_pdev(void)
+{
+ int err;
+
+ if (jnx_platform_device)
+ return 0; /* already initialized */
+
+ jnx_platform_device = platform_device_alloc("jnx", -1);
+ if (!jnx_platform_device)
+ return -ENOMEM;
+
+ jnx_platform_device->dev.groups = jnx_groups;
+ err = platform_device_add(jnx_platform_device);
+ if (err)
+ goto err_free_device;
+
+ return 0;
+
+err_free_device:
+ platform_device_put(jnx_platform_device);
+
+ return err;
+}
+
+int jnx_register_chassis(struct jnx_chassis_info *chinfo)
+{
+ int ret;
+ char object[JNX_BRD_I2C_NAME_LEN + 8];
+ char subobject[24];
+ char arg0[13];
+ char *envp[4];
+
+ if (!jnx_platform_device) {
+ ret = jnx_subsystem_init_pdev();
+ if (ret)
+ return ret;
+ }
+
+ envp[0] = object;
+ envp[1] = subobject;
+ envp[2] = arg0;
+ envp[3] = NULL;
+
+ chassis_info = *chinfo;
+
+ ret = sysfs_create_group(&jnx_platform_device->dev.kobj,
+ &jnx_chassis_group);
+ if (ret < 0)
+ return ret;
+
+ jnx_platform_uevent(NULL, "chassis", NULL, KOBJ_ADD);
+ if (chinfo->mastership_get) {
+ /* notify udev of mastership sysfs attr creation */
+ sprintf(arg0, "ARG0=%s",
+ chinfo->mastership_get(chinfo->master_data) ? "master" :
+ "standby");
+ sprintf(object, "OBJECT=chassis");
+ sprintf(subobject, "SUBOBJECT=mastership");
+ kobject_uevent_env(&jnx_platform_device->dev.kobj,
+ KOBJ_ADD, envp);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(jnx_register_chassis);
+
+void jnx_unregister_chassis(void)
+{
+ if (jnx_platform_device) {
+ sysfs_remove_group(&jnx_platform_device->dev.kobj,
+ &jnx_chassis_group);
+ jnx_platform_uevent(NULL, "chassis", NULL, KOBJ_REMOVE);
+ }
+}
+EXPORT_SYMBOL(jnx_unregister_chassis);
+
+static
+struct platform_device *jnx_create_card_device(char *name,
+ struct jnx_card_info *cinfo,
+ int id)
+{
+ struct platform_device *pdev;
+ int err;
+
+ pdev = platform_device_alloc(name, id);
+ if (!pdev)
+ return ERR_PTR(-ENOMEM);
+
+ err = platform_device_add_data(pdev, cinfo, sizeof(*cinfo));
+ if (err)
+ goto pdev_failure;
+
+ if (jnx_platform_device)
+ pdev->dev.parent = &jnx_platform_device->dev;
+
+ if (id != -1)
+ pdev->dev.groups = jnx_card_groups;
+ else
+ pdev->dev.groups = jnx_local_card_groups;
+
+ err = platform_device_add(pdev);
+ if (err)
+ goto pdev_failure;
+
+ return pdev;
+
+pdev_failure:
+ platform_device_put(pdev);
+ return ERR_PTR(err);
+}
+
+int jnx_sysfs_create_link(struct device *dev, const char *link)
+{
+ int ret = 0;
+
+ if (jnx_platform_device) {
+ ret = sysfs_add_link_to_group(&jnx_platform_device->dev.kobj,
+ "card", &dev->kobj, link);
+ if (!ret)
+ ret = jnx_platform_uevent("card", link, NULL, KOBJ_ADD);
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(jnx_sysfs_create_link);
+
+void jnx_sysfs_delete_link(struct device *dev, const char *link)
+{
+ if (jnx_platform_device) {
+ sysfs_remove_link_from_group(&jnx_platform_device->dev.kobj,
+ "card", link);
+ jnx_platform_uevent("card", link, NULL, KOBJ_REMOVE);
+ }
+}
+EXPORT_SYMBOL_GPL(jnx_sysfs_delete_link);
+
+/*
+ * Register local card. This is the card we are running on.
+ * Typically this would be the RE or a PMB.
+ * Create a card device similar to other card devices.
+ * Also create the 'local' link in the 'card' directory.
+ */
+int jnx_register_local_card(struct jnx_card_info *cinfo)
+{
+ char name[JNX_BRD_I2C_NAME_LEN];
+
+ if (jnx_local_card_device)
+ return -EEXIST;
+
+ snprintf(name, sizeof(name), "jnx-%04x-local", cinfo->assembly_id);
+ jnx_local_card_device = jnx_create_card_device(name, cinfo, -1);
+ if (IS_ERR(jnx_local_card_device))
+ return PTR_ERR(jnx_local_card_device);
+
+ jnx_sysfs_create_link(&jnx_local_card_device->dev, "local");
+
+ return 0;
+}
+EXPORT_SYMBOL(jnx_register_local_card);
+
+void jnx_unregister_local_card(void)
+{
+ if (jnx_local_card_device) {
+ jnx_sysfs_delete_link(&jnx_local_card_device->dev, "local");
+ platform_device_unregister(jnx_local_card_device);
+ jnx_local_card_device = NULL;
+ }
+}
+EXPORT_SYMBOL(jnx_unregister_local_card);
+
+int jnx_register_board(struct device *dev, struct device *ideeprom,
+ struct jnx_card_info *cinfo, int id)
+{
+ char name[JNX_BRD_I2C_NAME_LEN];
+ struct platform_device *pdev;
+ struct jnx_board_entry *entry;
+ int err;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "jnx-%04x", cinfo->assembly_id);
+ pdev = jnx_create_card_device(name, cinfo, id);
+ if (IS_ERR(pdev)) {
+ kfree(entry);
+ return PTR_ERR(pdev);
+ }
+
+ if (ideeprom) {
+ err = sysfs_create_link(&pdev->dev.kobj, &ideeprom->kobj, "id");
+ if (err)
+ dev_err(&pdev->dev,
+ "Failed to create link to ID eeprom\n");
+ }
+
+ if (cinfo->adap) {
+ err = sysfs_create_link(&pdev->dev.kobj,
+ &cinfo->adap->dev.kobj, "i2c-adapter");
+ if (err)
+ dev_err(&pdev->dev,
+ "Failed to create link to i2c adapter\n");
+ }
+ entry->pdev = pdev;
+ entry->dev = dev;
+
+ spin_lock(&jnx_board_list_lock);
+ list_add_tail(&entry->list, &jnx_board_list);
+ spin_unlock(&jnx_board_list_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(jnx_register_board);
+
+static struct jnx_board_entry *jnx_find_board_entry_by_dev(struct device *dev)
+{
+ struct jnx_board_entry *brd_entry;
+
+ spin_lock(&jnx_board_list_lock);
+
+ list_for_each_entry(brd_entry, &jnx_board_list, list) {
+ /*
+ * Device is either the device stored in brd_entry,
+ * or its parent (if there is a channel enable mux
+ * on the board).
+ */
+ if (brd_entry->dev == dev || brd_entry->dev->parent == dev)
+ goto found;
+ }
+
+ brd_entry = NULL;
+
+found:
+ spin_unlock(&jnx_board_list_lock);
+
+ return brd_entry;
+}
+
+int jnx_unregister_board(struct device *dev)
+{
+ struct jnx_board_entry *entry;
+
+ entry = jnx_find_board_entry_by_dev(dev);
+ if (!entry)
+ return -ENODEV;
+
+ if (!list_empty(&jnx_board_list)) {
+ spin_lock(&jnx_board_list_lock);
+ list_del(&entry->list);
+ spin_unlock(&jnx_board_list_lock);
+ }
+
+ sysfs_remove_link(&entry->pdev->dev.kobj, "id");
+ sysfs_remove_link(&entry->pdev->dev.kobj, "i2c-adapter");
+ platform_device_unregister(entry->pdev);
+
+ kfree(entry);
+
+ return 0;
+}
+EXPORT_SYMBOL(jnx_unregister_board);
+
+subsys_initcall(jnx_subsystem_init_pdev);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
diff --git a/include/linux/jnx/jnx-board-core.h b/include/linux/jnx/jnx-board-core.h
new file mode 100644
index 0000000..1b4747e
--- /dev/null
+++ b/include/linux/jnx/jnx-board-core.h
@@ -0,0 +1,41 @@
+/*
+ * Juniper Generic Board APIs
+ *
+ * Copyright (C) 2012, 2013 Juniper Networks. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef _JNX_BOARD_CORE_H
+#define _JNX_BOARD_CORE_H
+
+/*
+ * Generic Juniper board I2C bus notification list handling
+ */
+struct jnx_board_i2c_entry {
+ struct i2c_board_info *board_info;
+ int bi_num;
+ char name[JNX_BRD_I2C_NAME_LEN];
+ struct work_struct work;
+ unsigned long action;
+ struct device *dev;
+ struct list_head list;
+};
+
+struct i2c_adapter *jnx_i2c_find_adapter(char *name);
+struct i2c_client *jnx_board_inserted(struct i2c_adapter *adap, int slot,
+ bool has_mux);
+void jnx_board_removed(struct i2c_adapter *adap, struct i2c_client *client);
+
+/* API testing warmboot: != 0: warmboot, == 0: coldboot */
+bool jnx_warmboot(void);
+
+#endif /* _JNX_BOARD_CORE_H */
diff --git a/include/linux/jnx/jnx-subsys.h b/include/linux/jnx/jnx-subsys.h
new file mode 100644
index 0000000..404304b
--- /dev/null
+++ b/include/linux/jnx/jnx-subsys.h
@@ -0,0 +1,94 @@
+/*
+ * Juniper Generic APIs for providing chassis and card information
+ *
+ * Copyright (C) 2012, 2013, 2014 Juniper Networks. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef _JNX_SUBSYS_H
+#define _JNX_SUBSYS_H
+
+#include <uapi/linux/jnx/jnx-subsys.h>
+
+/*
+ * Juniper Product Number Definitions
+ */
+#define JNX_PRODUCT_HERCULES 7
+#define JNX_PRODUCT_SANGRIA 85
+#define JNX_PRODUCT_TINY 134
+#define JNX_PRODUCT_HENDRICKS 156
+#define JNX_PRODUCT_POLARIS 171
+#define JNX_PRODUCT_OMEGA 181
+
+#define JNX_BRD_I2C_NAME_LEN 24
+
+/* create and delete a link in jnx/card/<link> pointing to given dev */
+int jnx_sysfs_create_link(struct device *dev, const char *link);
+void jnx_sysfs_delete_link(struct device *dev, const char *link);
+
+/**
+ * struct jnx_card_info - juniper board per card information
+ * @assembly_id: assembly ID read from the EEPROM
+ * @slot: slot number in the chassis
+ * @type: type of card; see uapi jnx-subsys.h
+ * @data: per card user data
+ * @adap: pointer to the i2c_adapter EEPROM is on
+ *
+ * This structure contains information that each juniper board
+ * provides.
+ */
+struct jnx_card_info {
+ u16 assembly_id;
+ int slot;
+ u32 type;
+ void *data;
+ struct i2c_adapter *adap;
+};
+
+/* register and unregister a local jnx card */
+int jnx_register_local_card(struct jnx_card_info *cinfo);
+void jnx_unregister_local_card(void);
+
+/**
+ * struct jnx_chassis_info - juniper chassis info and method callbacks
+ * @platform: platform id of the chassis
+ * @chassis_no: chassis number - 0 if non-multi chassis
+ * @multichassis: non zero if a multichassis system
+ * @master_data: per chassis data
+ * @get_master: get slot number of master
+ * @mastership_get: returns whether we're master
+ * @mastership_set: Relinquish mastership
+ * @mastership_ping: update mastership watchdog
+ * @mastership_count_get: get mastership watchdog counter
+ * @mastership_count_set: set mastership watchdog counter
+ *
+ * This structure contains per chassis information and method callbacks that
+ * handle the per-platform CBD FPGA differences.
+ */
+struct jnx_chassis_info {
+ u32 platform;
+ u32 chassis_no;
+ u32 multichassis;
+ void *master_data;
+ int (*get_master)(void *data);
+ bool (*mastership_get)(void *data);
+ void (*mastership_set)(void *data, bool mastership);
+ void (*mastership_ping)(void *data);
+ int (*mastership_count_get)(void *data);
+ int (*mastership_count_set)(void *data, int val);
+};
+
+/* register and unregsiter a juniper chassis */
+int jnx_register_chassis(struct jnx_chassis_info *chinfo);
+void jnx_unregister_chassis(void);
+
+#endif /* _JNX_SUBSYS_H */
--
1.9.1