|  | /* | 
|  | * Copyright(c) 2015, 2016 Intel Corporation. | 
|  | * | 
|  | * This file is provided under a dual BSD/GPLv2 license.  When using or | 
|  | * redistributing this file, you may do so under either license. | 
|  | * | 
|  | * GPL LICENSE SUMMARY | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify | 
|  | * it under the terms of version 2 of the GNU General Public License as | 
|  | * published by the Free Software Foundation. | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | * BSD LICENSE | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | * | 
|  | *  - Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | *  - Redistributions in binary form must reproduce the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer in | 
|  | *    the documentation and/or other materials provided with the | 
|  | *    distribution. | 
|  | *  - Neither the name of Intel Corporation nor the names of its | 
|  | *    contributors may be used to endorse or promote products derived | 
|  | *    from this software without specific prior written permission. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | * | 
|  | */ | 
|  | #include <linux/delay.h> | 
|  | #include "hfi.h" | 
|  | #include "common.h" | 
|  | #include "eprom.h" | 
|  |  | 
|  | /* | 
|  | * The EPROM is logically divided into three partitions: | 
|  | *	partition 0: the first 128K, visible from PCI ROM BAR | 
|  | *	partition 1: 4K config file (sector size) | 
|  | *	partition 2: the rest | 
|  | */ | 
|  | #define P0_SIZE (128 * 1024) | 
|  | #define P1_SIZE   (4 * 1024) | 
|  | #define P1_START P0_SIZE | 
|  | #define P2_START (P0_SIZE + P1_SIZE) | 
|  |  | 
|  | /* controller page size, in bytes */ | 
|  | #define EP_PAGE_SIZE 256 | 
|  | #define EP_PAGE_MASK (EP_PAGE_SIZE - 1) | 
|  | #define EP_PAGE_DWORDS (EP_PAGE_SIZE / sizeof(u32)) | 
|  |  | 
|  | /* controller commands */ | 
|  | #define CMD_SHIFT 24 | 
|  | #define CMD_NOP			    (0) | 
|  | #define CMD_READ_DATA(addr)	    ((0x03 << CMD_SHIFT) | addr) | 
|  | #define CMD_RELEASE_POWERDOWN_NOID  ((0xab << CMD_SHIFT)) | 
|  |  | 
|  | /* controller interface speeds */ | 
|  | #define EP_SPEED_FULL 0x2	/* full speed */ | 
|  |  | 
|  | /* | 
|  | * How long to wait for the EPROM to become available, in ms. | 
|  | * The spec 32 Mb EPROM takes around 40s to erase then write. | 
|  | * Double it for safety. | 
|  | */ | 
|  | #define EPROM_TIMEOUT 80000 /* ms */ | 
|  |  | 
|  | /* | 
|  | * Read a 256 byte (64 dword) EPROM page. | 
|  | * All callers have verified the offset is at a page boundary. | 
|  | */ | 
|  | static void read_page(struct hfi1_devdata *dd, u32 offset, u32 *result) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | write_csr(dd, ASIC_EEP_ADDR_CMD, CMD_READ_DATA(offset)); | 
|  | for (i = 0; i < EP_PAGE_DWORDS; i++) | 
|  | result[i] = (u32)read_csr(dd, ASIC_EEP_DATA); | 
|  | write_csr(dd, ASIC_EEP_ADDR_CMD, CMD_NOP); /* close open page */ | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Read length bytes starting at offset from the start of the EPROM. | 
|  | */ | 
|  | static int read_length(struct hfi1_devdata *dd, u32 start, u32 len, void *dest) | 
|  | { | 
|  | u32 buffer[EP_PAGE_DWORDS]; | 
|  | u32 end; | 
|  | u32 start_offset; | 
|  | u32 read_start; | 
|  | u32 bytes; | 
|  |  | 
|  | if (len == 0) | 
|  | return 0; | 
|  |  | 
|  | end = start + len; | 
|  |  | 
|  | /* | 
|  | * Make sure the read range is not outside of the controller read | 
|  | * command address range.  Note that '>' is correct below - the end | 
|  | * of the range is OK if it stops at the limit, but no higher. | 
|  | */ | 
|  | if (end > (1 << CMD_SHIFT)) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* read the first partial page */ | 
|  | start_offset = start & EP_PAGE_MASK; | 
|  | if (start_offset) { | 
|  | /* partial starting page */ | 
|  |  | 
|  | /* align and read the page that contains the start */ | 
|  | read_start = start & ~EP_PAGE_MASK; | 
|  | read_page(dd, read_start, buffer); | 
|  |  | 
|  | /* the rest of the page is available data */ | 
|  | bytes = EP_PAGE_SIZE - start_offset; | 
|  |  | 
|  | if (len <= bytes) { | 
|  | /* end is within this page */ | 
|  | memcpy(dest, (u8 *)buffer + start_offset, len); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | memcpy(dest, (u8 *)buffer + start_offset, bytes); | 
|  |  | 
|  | start += bytes; | 
|  | len -= bytes; | 
|  | dest += bytes; | 
|  | } | 
|  | /* start is now page aligned */ | 
|  |  | 
|  | /* read whole pages */ | 
|  | while (len >= EP_PAGE_SIZE) { | 
|  | read_page(dd, start, buffer); | 
|  | memcpy(dest, buffer, EP_PAGE_SIZE); | 
|  |  | 
|  | start += EP_PAGE_SIZE; | 
|  | len -= EP_PAGE_SIZE; | 
|  | dest += EP_PAGE_SIZE; | 
|  | } | 
|  |  | 
|  | /* read the last partial page */ | 
|  | if (len) { | 
|  | read_page(dd, start, buffer); | 
|  | memcpy(dest, buffer, len); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Initialize the EPROM handler. | 
|  | */ | 
|  | int eprom_init(struct hfi1_devdata *dd) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | /* only the discrete chip has an EPROM */ | 
|  | if (dd->pcidev->device != PCI_DEVICE_ID_INTEL0) | 
|  | return 0; | 
|  |  | 
|  | /* | 
|  | * It is OK if both HFIs reset the EPROM as long as they don't | 
|  | * do it at the same time. | 
|  | */ | 
|  | ret = acquire_chip_resource(dd, CR_EPROM, EPROM_TIMEOUT); | 
|  | if (ret) { | 
|  | dd_dev_err(dd, | 
|  | "%s: unable to acquire EPROM resource, no EPROM support\n", | 
|  | __func__); | 
|  | goto done_asic; | 
|  | } | 
|  |  | 
|  | /* reset EPROM to be sure it is in a good state */ | 
|  |  | 
|  | /* set reset */ | 
|  | write_csr(dd, ASIC_EEP_CTL_STAT, ASIC_EEP_CTL_STAT_EP_RESET_SMASK); | 
|  | /* clear reset, set speed */ | 
|  | write_csr(dd, ASIC_EEP_CTL_STAT, | 
|  | EP_SPEED_FULL << ASIC_EEP_CTL_STAT_RATE_SPI_SHIFT); | 
|  |  | 
|  | /* wake the device with command "release powerdown NoID" */ | 
|  | write_csr(dd, ASIC_EEP_ADDR_CMD, CMD_RELEASE_POWERDOWN_NOID); | 
|  |  | 
|  | dd->eprom_available = true; | 
|  | release_chip_resource(dd, CR_EPROM); | 
|  | done_asic: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* magic character sequence that trails an image */ | 
|  | #define IMAGE_TRAIL_MAGIC "egamiAPO" | 
|  |  | 
|  | /* | 
|  | * Read all of partition 1.  The actual file is at the front.  Adjust | 
|  | * the returned size if a trailing image magic is found. | 
|  | */ | 
|  | static int read_partition_platform_config(struct hfi1_devdata *dd, void **data, | 
|  | u32 *size) | 
|  | { | 
|  | void *buffer; | 
|  | void *p; | 
|  | u32 length; | 
|  | int ret; | 
|  |  | 
|  | buffer = kmalloc(P1_SIZE, GFP_KERNEL); | 
|  | if (!buffer) | 
|  | return -ENOMEM; | 
|  |  | 
|  | ret = read_length(dd, P1_START, P1_SIZE, buffer); | 
|  | if (ret) { | 
|  | kfree(buffer); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* scan for image magic that may trail the actual data */ | 
|  | p = strnstr(buffer, IMAGE_TRAIL_MAGIC, P1_SIZE); | 
|  | if (p) | 
|  | length = p - buffer; | 
|  | else | 
|  | length = P1_SIZE; | 
|  |  | 
|  | *data = buffer; | 
|  | *size = length; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Read the platform configuration file from the EPROM. | 
|  | * | 
|  | * On success, an allocated buffer containing the data and its size are | 
|  | * returned.  It is up to the caller to free this buffer. | 
|  | * | 
|  | * Return value: | 
|  | *   0	      - success | 
|  | *   -ENXIO   - no EPROM is available | 
|  | *   -EBUSY   - not able to acquire access to the EPROM | 
|  | *   -ENOENT  - no recognizable file written | 
|  | *   -ENOMEM  - buffer could not be allocated | 
|  | */ | 
|  | int eprom_read_platform_config(struct hfi1_devdata *dd, void **data, u32 *size) | 
|  | { | 
|  | u32 directory[EP_PAGE_DWORDS]; /* aligned buffer */ | 
|  | int ret; | 
|  |  | 
|  | if (!dd->eprom_available) | 
|  | return -ENXIO; | 
|  |  | 
|  | ret = acquire_chip_resource(dd, CR_EPROM, EPROM_TIMEOUT); | 
|  | if (ret) | 
|  | return -EBUSY; | 
|  |  | 
|  | /* read the last page of P0 for the EPROM format magic */ | 
|  | ret = read_length(dd, P1_START - EP_PAGE_SIZE, EP_PAGE_SIZE, directory); | 
|  | if (ret) | 
|  | goto done; | 
|  |  | 
|  | /* last dword of P0 contains a magic indicator */ | 
|  | if (directory[EP_PAGE_DWORDS - 1] == 0) { | 
|  | /* partition format */ | 
|  | ret = read_partition_platform_config(dd, data, size); | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | /* nothing recognized */ | 
|  | ret = -ENOENT; | 
|  |  | 
|  | done: | 
|  | release_chip_resource(dd, CR_EPROM); | 
|  | return ret; | 
|  | } |