| /* |
| <:copyright-BRCM:2016:DUAL/GPL:standard |
| |
| Broadcom Proprietary and Confidential.(c) 2016 Broadcom |
| All Rights Reserved |
| |
| Unless you and Broadcom execute a separate written software license |
| agreement governing use of this software, this software is licensed |
| to you under the terms of the GNU General Public License version 2 |
| (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, |
| with the following added to such license: |
| |
| As a special exception, the copyright holders of this software give |
| you permission to link this software with independent modules, and |
| to copy and distribute the resulting executable under terms of your |
| choice, provided that you also meet, for each linked independent |
| module, the terms and conditions of the license of that module. |
| An independent module is a module which is not derived from this |
| software. The special exception does not apply to any modifications |
| of the software. |
| |
| Not withstanding the above, under no circumstances may you combine |
| this software in any way with any other Broadcom software provided |
| under a license other than the GPL, without Broadcom's express prior |
| written consent. |
| |
| :> |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/buffer_head.h> |
| #include <linux/reboot.h> |
| #include <linux/pci.h> |
| #include <asm/segment.h> |
| #include <asm/uaccess.h> |
| #include <bcmos_system.h> |
| #include <bcm_config.h> |
| #include <bcmolt_msg.h> |
| #include <bcmolt_fld.h> |
| #include <bcmolt_llpcie.h> |
| #include <bcmolt_conv.h> |
| #include <bcmolt_i2c_devs.h> |
| #include <bcmolt_board_selector.h> |
| #include "bcmolt_user_utils.h" |
| |
| #define BCM_I2C_DEV_ADDR_START typedef enum { |
| #define BCM_I2C_DEV_ADDR(name, desc, val) name = val, |
| #define BCM_I2C_DEV_ADDR_END } bcm_i2c_dev_addr; |
| #include <bcmolt_i2c_devs_addr.h> |
| |
| #define BOOT_FILE "/opt/bcm68620/bcm68620_boot.bin" |
| #define APPL_FILE "/opt/bcm68620/bcm68620_appl.bin" |
| #define ONU_FW_FILE "/opt/bcm68620/dummy_onu_firmware.bin" |
| |
| #define FPGA_REG_FPGA_VERSION 0x0 |
| #define FPGA_REG_BOARD_CONF 0x1 |
| #define FPGA_REG_RESETS 0x2 |
| |
| #define FPGA_RST_BIT_DEVICE 0x0 |
| #define FPGA_DEVICE_SWITCH 4 |
| |
| #define FPGA_ENABLE_ALL_DEVICES 0x1f |
| #define FPGA_ENABLE_SWITCH (1 << FPGA_DEVICE_SWITCH) |
| /* For now swap devices for consistency with PCI enumeration */ |
| #define FPGA_DEVICE_RESET_BIT(dev) (1 << (FPGA_RST_BIT_DEVICE + dev)) |
| |
| /* Minimum required FPGA version for reading default system mode via BOARD_CONF register. */ |
| #define FPGA_VER_FOR_SVK_CONFIG 6 |
| |
| /* Lock that protects critical sections */ |
| static bcmos_mutex user_lock; |
| static bcmos_bool user_lock_initialized; |
| |
| typedef enum |
| { |
| SVK_BOARD_ID_1 = 0x7, /* SVK #1 - BCM968620S: all PON flavors supported, SFP+ */ |
| SVK_BOARD_ID_2_XG = 0x6, /* SVK #2 - BCM968620XG: XGPON */ |
| SVK_BOARD_ID_2_XE = 0x5, /* SVK #2 - BCM968620XE: 10G EPON */ |
| SVK_BOARD_ID_3 = 0x4, /* SVK #3 - BCM968620K: all PON flavors supported, XFP */ |
| DVT_BOARD_ID = 0x3, /* DVT board */ |
| } svk_board_id; |
| |
| typedef enum |
| { |
| SVK_MODE_GPON_16X = 0xF, /* SVK #1/3: 16x GPON mode, SVK #2: only valid mode */ |
| SVK_MODE_EPON_1G_16X = 0xE, /* SVK #1/3: 16x 1G EPON mode */ |
| SVK_MODE_XGPON = 0xD, /* SVK #1/3: 8x XGPON mode */ |
| SVK_MODE_EPON_10G_8X = 0xC, /* SVK #1/3: 8x 10G EPON mode */ |
| SVK_MODE_EPON_GPON_8X = 0xB, /* SVK #1/3: 8x 10G EPON + 8x GPON coexistence mode */ |
| } svk_mode; |
| |
| |
| #define CHECK_RETURN_ERROR(cond,rc) \ |
| do { \ |
| if (cond) {\ |
| bcmos_printf("%s#%d: failed with rc=%d\n", __FUNCTION__, __LINE__, rc);\ |
| return rc;\ |
| }\ |
| } while (0) |
| |
| |
| static void userlock_lock(void) |
| { |
| if (!user_lock_initialized) |
| { |
| bcmos_mutex_create(&user_lock, 0, "reset_lock"); |
| user_lock_initialized = BCMOS_TRUE; |
| } |
| bcmos_mutex_lock(&user_lock); |
| } |
| |
| |
| static void userlock_unlock(void) |
| { |
| bcmos_mutex_unlock(&user_lock); |
| } |
| |
| static struct file *file_open(const char *path, int flags) |
| { |
| struct file *fd = NULL; |
| mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; |
| |
| fd = filp_open(path, flags, mode); |
| if(IS_ERR(fd)) |
| return NULL; |
| return fd; |
| } |
| |
| static void file_close(struct file *file) |
| { |
| filp_close(file, NULL); |
| } |
| |
| static int file_read(struct file *file, uint32_t offset, uint8_t *data, unsigned int size) |
| { |
| unsigned long long o = offset; |
| mm_segment_t oldfs; |
| int bytes_read; |
| |
| oldfs = get_fs(); |
| set_fs(get_ds()); |
| bytes_read = vfs_read(file, data, size, &o); |
| set_fs(oldfs); |
| if (bytes_read < 0) |
| { |
| BCMOS_TRACE_ERR("vfs_read returned error %d\n", bytes_read); |
| return (int)BCM_ERR_IO; |
| } |
| return bytes_read; |
| } |
| |
| static int file_write(struct file* file, unsigned long long offset, unsigned char* data, unsigned int size) |
| { |
| mm_segment_t oldfs; |
| int ret; |
| |
| oldfs = get_fs(); |
| set_fs(get_ds()); |
| |
| ret = vfs_write(file, data, size, &offset); |
| |
| set_fs(oldfs); |
| return ret; |
| } |
| |
| static bcmos_errno file_write_1(const char *path) |
| { |
| struct file *fd; |
| char to_write[] = { '1' }; |
| loff_t pos = 0; |
| ssize_t bytes_written; |
| |
| fd = file_open(path, O_WRONLY); |
| if ((fd == NULL) || (fd == (void *)-ENOENT)) /* -ENOENT; No such file or directory */ |
| { |
| BCMOS_TRACE_ERR("Can't open file %s for writing. Error %s\n", |
| path, bcmos_strerror(fd == NULL ? BCM_ERR_NULL : BCM_ERR_NOENT)); |
| return (fd == NULL ? BCM_ERR_NULL : BCM_ERR_NOENT); |
| } |
| |
| bytes_written = file_write(fd, pos, to_write, sizeof(to_write)); |
| if (bytes_written != sizeof(to_write)) |
| { |
| BCMOS_TRACE_ERR("vfs_write returned incorrect number of characters written: %d\n", bytes_written); |
| } |
| |
| file_close(fd); |
| return BCM_ERR_OK; |
| } |
| |
| static bcmos_errno i2c_fpga_read(uint32_t reg, uint32_t *val) |
| { |
| return maple_i2c_read_fpga(maple_i2c_get_client(FPGA_I2C_ADDR), reg, val); |
| } |
| |
| static bcmos_errno i2c_fpga_write(uint32_t reg, uint32_t val) |
| { |
| return maple_i2c_write_fpga(maple_i2c_get_client(FPGA_I2C_ADDR), reg, val); |
| } |
| |
| static bcmos_errno i2c_config_switch(void) |
| { |
| uint8_t to_write = 1; |
| int ret; |
| |
| ret = maple_i2c_write(maple_i2c_get_client(I2C_SW0_I2C_ADDR), &to_write, 1); |
| if (ret != 0) |
| { |
| BCMOS_TRACE_ERR("Failed to write switch: %d\n", ret); |
| return BCM_ERR_INTERNAL; |
| } |
| |
| return BCM_ERR_OK; |
| } |
| |
| static bcmos_errno i2c_fpga_version_get(uint32_t *version) |
| { |
| return i2c_fpga_read(FPGA_REG_FPGA_VERSION, version); |
| } |
| |
| static bcmos_errno i2c_svk_config_get(svk_board_id *board_id, svk_mode *mode) |
| { |
| uint32_t val; |
| bcmos_errno rc; |
| |
| rc = i2c_fpga_read(FPGA_REG_BOARD_CONF, &val); |
| if (rc == BCM_ERR_OK) |
| { |
| *board_id = (svk_board_id)(val & 0x7); |
| *mode = (svk_mode)((val >> 4) & 0xF); |
| } |
| |
| return rc; |
| } |
| |
| static bcmos_errno i2c_device_cpu_state_set(bcmolt_devid device, bcmos_bool is_on) |
| { |
| bcmos_errno rc; |
| uint32_t reset_values; |
| |
| /* Prevent race condition when this function is called for 2 devices simultaneously */ |
| userlock_lock(); |
| |
| do |
| { |
| rc = i2c_config_switch(); |
| if (rc) |
| break; |
| |
| /* Read-modify-write so we can set only the relevant bit */ |
| rc = i2c_fpga_read(FPGA_REG_RESETS, &reset_values); |
| if (rc) |
| break; |
| |
| if (is_on) |
| reset_values |= FPGA_DEVICE_RESET_BIT(device); |
| else |
| reset_values &= ~FPGA_DEVICE_RESET_BIT(device); |
| |
| rc = i2c_fpga_write(FPGA_REG_RESETS, reset_values); |
| |
| } while (0); |
| |
| userlock_unlock(); |
| |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| bcmos_usleep(1000); |
| |
| return BCM_ERR_OK; |
| } |
| |
| static bcmos_bool bcmuser_system_mode_is_valid(bcmolt_system_mode system_mode, svk_board_id board_id, svk_mode svk_mode) |
| { |
| switch (board_id) |
| { |
| case SVK_BOARD_ID_1: |
| case SVK_BOARD_ID_3: |
| switch (svk_mode) |
| { |
| case SVK_MODE_GPON_16X: |
| return (system_mode == BCMOLT_SYSTEM_MODE_GPON__4_X || |
| system_mode == BCMOLT_SYSTEM_MODE_GPON__8_X || |
| system_mode == BCMOLT_SYSTEM_MODE_GPON__16_X || |
| system_mode == BCMOLT_SYSTEM_MODE_GPON_8_XGPON_4_X_COEXISTENCE || |
| system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__8_X || |
| system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__4_X || |
| system_mode == BCMOLT_SYSTEM_MODE_NGPON2__8_X_2_P_5_G); |
| case SVK_MODE_EPON_1G_16X: |
| return (system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X || |
| system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X || |
| system_mode == BCMOLT_SYSTEM_MODE_EPON__16_X || |
| system_mode == BCMOLT_SYSTEM_MODE_EPON__2_X_10_G || |
| system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X_10_G || |
| system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_10_G); |
| case SVK_MODE_XGPON: |
| return (system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__8_X || |
| system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__4_X || |
| system_mode == BCMOLT_SYSTEM_MODE_NGPON2__8_X_2_P_5_G); |
| case SVK_MODE_EPON_10G_8X: |
| return |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__2_X_10_G) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X_10_G) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_10_G); |
| default: |
| return BCMOS_FALSE; |
| } |
| case SVK_BOARD_ID_2_XG: |
| return (system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__8_X || |
| system_mode == BCMOLT_SYSTEM_MODE_XGPON_1__4_X || |
| system_mode == BCMOLT_SYSTEM_MODE_NGPON2__8_X_2_P_5_G || |
| system_mode == BCMOLT_SYSTEM_MODE_XGS__2_X_10_G || |
| system_mode == BCMOLT_SYSTEM_MODE_NGPON2__2_X_10_G); |
| case SVK_BOARD_ID_2_XE: |
| return |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X_COEXISTENCE_TDMA) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_COEXISTENCE_TDMA) || |
| (system_mode == BCMOLT_SYSTEM_MODE_AE_8_X) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__2_X_10_G) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__4_X_10_G) || |
| (system_mode == BCMOLT_SYSTEM_MODE_EPON__8_X_10_G); |
| case DVT_BOARD_ID: |
| return BCMOS_TRUE; |
| default: |
| return BCMOS_FALSE; |
| } |
| } |
| |
| bcmos_errno bcmuser_system_mode_validate(bcmolt_devid device, bcmolt_system_mode system_mode) |
| { |
| bcmos_errno rc; |
| uint32_t version; |
| svk_board_id board_id; |
| svk_mode svk_mode; |
| |
| rc = i2c_config_switch(); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| rc = i2c_fpga_version_get(&version); |
| if (rc != BCM_ERR_OK) |
| { |
| BCMOS_TRACE_INFO("Failed to read FPGA version\n"); |
| return rc; |
| } |
| |
| if (version < FPGA_VER_FOR_SVK_CONFIG) |
| { |
| /* this FPGA version don't support reading the SVK version, so we must assume it's OK */ |
| return BCM_ERR_OK; |
| } |
| |
| rc = i2c_svk_config_get(&board_id, &svk_mode); |
| if (rc != BCM_ERR_OK) |
| { |
| BCMOS_TRACE_INFO("Failed to read SVK config\n"); |
| return rc; |
| } |
| |
| if (bcmuser_system_mode_is_valid(system_mode, board_id, svk_mode)) |
| { |
| return BCM_ERR_OK; |
| } |
| else |
| { |
| BCMOS_TRACE_INFO( |
| "System mode %s is not supported for board ID %d, SVK mode %d\n", |
| bcmolt_system_mode_name(system_mode), |
| board_id, |
| svk_mode); |
| return BCM_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| int bcmuser_image_read(bcmolt_devid device, bcmolt_device_image_type image_type, |
| uint32_t offset, uint8_t *buf, uint32_t buf_size) |
| { |
| struct file *fd; |
| int bytes_read; |
| bcmos_errno rc; |
| static int2str_t image_type2str[] = |
| { |
| {BCMOLT_DEVICE_IMAGE_TYPE_BOOTLOADER, BOOT_FILE}, |
| {BCMOLT_DEVICE_IMAGE_TYPE_APPLICATION, APPL_FILE}, |
| {BCMOLT_DEVICE_IMAGE_TYPE_ITU_PON_ONU_FIRMWARE, ONU_FW_FILE}, |
| {-1} |
| }; |
| const char *filename; |
| |
| filename = int2str(image_type2str, image_type); |
| fd = file_open(filename, O_RDONLY); |
| if ((fd == NULL) || (fd == (void *)-ENOENT)) /* -ENOENT; No such file or directory */ |
| { |
| rc = (fd == NULL ? BCM_ERR_NULL : BCM_ERR_NOENT); |
| BCMOS_TRACE_ERR("filp_open returned error %s (%d)\n", bcmos_strerror(rc), rc); |
| return rc; |
| } |
| bytes_read = file_read(fd, offset, buf, buf_size); |
| file_close(fd); |
| if (bytes_read < 0) |
| { |
| return (int)BCM_ERR_IO; |
| } |
| return bytes_read; |
| } |
| |
| bcmos_errno bcmuser_device_off(bcmolt_devid device) |
| { |
| /* Ignore request for non-existing device */ |
| if (device >= bcmolt_board_num_maple_devices()) |
| return BCM_ERR_OK; |
| BCMOS_TRACE_INFO("device=%u\n", device); |
| return i2c_device_cpu_state_set(device, BCMOS_FALSE); |
| } |
| |
| bcmos_errno bcmuser_device_on(bcmolt_devid device) |
| { |
| bcmos_errno rc; |
| |
| BCMOS_TRACE_INFO("device=%u\n", device); |
| if (device >= bcmolt_board_num_maple_devices()) |
| return BCM_ERR_RANGE; |
| |
| rc = i2c_device_cpu_state_set(device, BCMOS_TRUE); |
| /* Wait a moment to make sure the device is discoverable in case it just booted. */ |
| bcmos_usleep(50 * 1000); |
| |
| return rc; |
| } |
| |
| bcmos_errno bcmuser_device_is_running(bcmolt_devid device, bcmos_bool *is_running) |
| { |
| bcmos_errno rc; |
| uint32_t reset_values; |
| uint32_t version; |
| |
| rc = i2c_config_switch(); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| rc = i2c_fpga_version_get(&version); |
| if (rc != BCM_ERR_OK) |
| { |
| BCMOS_TRACE_INFO("Failed to read FPGA version\n"); |
| return rc; |
| } |
| if (!bcmolt_board_supports_standalone(version)) |
| { |
| /* For older FPGAs that don't support standalone mode, the device is taken out of reset at boot. We need to lie |
| and tell device control that it's not running, so it will be programmed from scratch. */ |
| BCMOS_TRACE_INFO("FPGA version %d doesn't support standalone mode\n", version); |
| *is_running = BCMOS_FALSE; |
| return BCM_ERR_OK; |
| } |
| |
| rc = i2c_fpga_read(FPGA_REG_RESETS, &reset_values); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| *is_running = ((reset_values & FPGA_DEVICE_RESET_BIT(device)) != 0); |
| |
| return BCM_ERR_OK; |
| } |
| |
| bcmos_errno bcmuser_host_reset(void) |
| { |
| bcm_ll_pcie_cleanup(); |
| kernel_restart(NULL); |
| return BCM_ERR_OK; /* will never be reached */ |
| } |
| |
| bcmos_errno bcmuser_pcie_prepare(void) |
| { |
| bcmos_errno rc; |
| uint32_t reset_values; |
| const char *pcie_rescan_file = bcmolt_board_pci_rescan_string_get(); |
| int i; |
| |
| /* - Take all devices out of reset |
| * - rescan bus |
| * - restore reset value |
| */ |
| |
| rc = i2c_config_switch(); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| /* Read-modify-write so we can set only the relevant bit */ |
| rc = i2c_fpga_read(FPGA_REG_RESETS, &reset_values); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| rc = i2c_fpga_write(FPGA_REG_RESETS, FPGA_ENABLE_ALL_DEVICES); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| bcmos_usleep(50000); |
| |
| /* Now recan PCI bus */ |
| /* Now re-enumerate the PCI bus by writing a 1 to the 'rescan' file. */ |
| rc = file_write_1(pcie_rescan_file); |
| if (rc != BCM_ERR_OK) |
| BCMOS_TRACE_ERR("file write returned error %s (%d)\n", bcmos_strerror(rc), rc); |
| bcmos_usleep(100 * 1000); |
| |
| /* Now initialize ll_pcie driver and map maple devices */ |
| rc = bcm_ll_pcie_init(); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| /* Now restore rest values. Keep switch out of reset */ |
| for (i = 0; i < bcmolt_board_num_maple_devices(); i++) |
| { |
| if ((FPGA_DEVICE_RESET_BIT(i) & reset_values) == 0) |
| bcmuser_pcie_channel_remove(i); |
| } |
| rc = i2c_fpga_write(FPGA_REG_RESETS, reset_values | FPGA_ENABLE_SWITCH); |
| CHECK_RETURN_ERROR(rc != BCM_ERR_OK, rc); |
| |
| return 0; |
| } |
| |
| bcmos_errno bcmuser_pcie_channel_prepare(bcmolt_devid device) |
| { |
| bcmos_errno rc; |
| |
| rc = bcm_ll_pcie_dev_enable(device); |
| if (rc) |
| BCMOS_TRACE_ERR("Couldn't prepare device %d. Error %s\n", device, bcmos_strerror(rc)); |
| |
| return rc; |
| } |
| |
| bcmos_errno bcmuser_pcie_channel_remove(bcmolt_devid device) |
| { |
| bcm_ll_pcie_dev_disable(device); |
| return BCM_ERR_OK; |
| } |