| /***********************license start*************** |
| * Author: Cavium Networks |
| * |
| * Contact: support@caviumnetworks.com |
| * This file is part of the OCTEON SDK |
| * |
| * Copyright (c) 2003-2010 Cavium Networks |
| * |
| * This file is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License, Version 2, as |
| * published by the Free Software Foundation. |
| * |
| * This file is distributed in the hope that it will be useful, but |
| * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty |
| * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or |
| * NONINFRINGEMENT. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this file; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * or visit http://www.gnu.org/licenses/. |
| * |
| * This file may also be available under a different license from Cavium. |
| * Contact Cavium Networks for more information |
| ***********************license end**************************************/ |
| |
| #include <asm/octeon/octeon.h> |
| |
| enum octeon_feature_bits __octeon_feature_bits __read_mostly; |
| EXPORT_SYMBOL_GPL(__octeon_feature_bits); |
| |
| /** |
| * Read a byte of fuse data |
| * @byte_addr: address to read |
| * |
| * Returns fuse value: 0 or 1 |
| */ |
| static uint8_t __init cvmx_fuse_read_byte(int byte_addr) |
| { |
| union cvmx_mio_fus_rcmd read_cmd; |
| |
| read_cmd.u64 = 0; |
| read_cmd.s.addr = byte_addr; |
| read_cmd.s.pend = 1; |
| cvmx_write_csr(CVMX_MIO_FUS_RCMD, read_cmd.u64); |
| while ((read_cmd.u64 = cvmx_read_csr(CVMX_MIO_FUS_RCMD)) |
| && read_cmd.s.pend) |
| ; |
| return read_cmd.s.dat; |
| } |
| |
| /* |
| * Version of octeon_model_get_string() that takes buffer as argument, |
| * as running early in u-boot static/global variables don't work when |
| * running from flash. |
| */ |
| static const char *__init octeon_model_get_string_buffer(uint32_t chip_id, |
| char *buffer) |
| { |
| const char *family; |
| const char *core_model; |
| char pass[4]; |
| int clock_mhz; |
| const char *suffix; |
| union cvmx_l2d_fus3 fus3; |
| int num_cores; |
| union cvmx_mio_fus_dat2 fus_dat2; |
| union cvmx_mio_fus_dat3 fus_dat3; |
| char fuse_model[10]; |
| uint32_t fuse_data = 0; |
| |
| fus3.u64 = 0; |
| if (!OCTEON_IS_MODEL(OCTEON_CN6XXX)) |
| fus3.u64 = cvmx_read_csr(CVMX_L2D_FUS3); |
| fus_dat2.u64 = cvmx_read_csr(CVMX_MIO_FUS_DAT2); |
| fus_dat3.u64 = cvmx_read_csr(CVMX_MIO_FUS_DAT3); |
| num_cores = cvmx_pop(cvmx_read_csr(CVMX_CIU_FUSE)); |
| |
| /* Make sure the non existent devices look disabled */ |
| switch ((chip_id >> 8) & 0xff) { |
| case 6: /* CN50XX */ |
| case 2: /* CN30XX */ |
| fus_dat3.s.nodfa_dte = 1; |
| fus_dat3.s.nozip = 1; |
| break; |
| case 4: /* CN57XX or CN56XX */ |
| fus_dat3.s.nodfa_dte = 1; |
| break; |
| default: |
| break; |
| } |
| |
| /* Make a guess at the suffix */ |
| /* NSP = everything */ |
| /* EXP = No crypto */ |
| /* SCP = No DFA, No zip */ |
| /* CP = No DFA, No crypto, No zip */ |
| if (fus_dat3.s.nodfa_dte) { |
| if (fus_dat2.s.nocrypto) |
| suffix = "CP"; |
| else |
| suffix = "SCP"; |
| } else if (fus_dat2.s.nocrypto) |
| suffix = "EXP"; |
| else |
| suffix = "NSP"; |
| |
| if (!fus_dat2.s.nocrypto) |
| __octeon_feature_bits |= OCTEON_HAS_CRYPTO; |
| |
| /* |
| * Assume pass number is encoded using <5:3><2:0>. Exceptions |
| * will be fixed later. |
| */ |
| sprintf(pass, "%d.%d", (int)((chip_id >> 3) & 7) + 1, (int)chip_id & 7); |
| |
| /* |
| * Use the number of cores to determine the last 2 digits of |
| * the model number. There are some exceptions that are fixed |
| * later. |
| */ |
| switch (num_cores) { |
| case 32: |
| core_model = "80"; |
| break; |
| case 24: |
| core_model = "70"; |
| break; |
| case 16: |
| core_model = "60"; |
| break; |
| case 15: |
| core_model = "58"; |
| break; |
| case 14: |
| core_model = "55"; |
| break; |
| case 13: |
| core_model = "52"; |
| break; |
| case 12: |
| core_model = "50"; |
| break; |
| case 11: |
| core_model = "48"; |
| break; |
| case 10: |
| core_model = "45"; |
| break; |
| case 9: |
| core_model = "42"; |
| break; |
| case 8: |
| core_model = "40"; |
| break; |
| case 7: |
| core_model = "38"; |
| break; |
| case 6: |
| core_model = "34"; |
| break; |
| case 5: |
| core_model = "32"; |
| break; |
| case 4: |
| core_model = "30"; |
| break; |
| case 3: |
| core_model = "25"; |
| break; |
| case 2: |
| core_model = "20"; |
| break; |
| case 1: |
| core_model = "10"; |
| break; |
| default: |
| core_model = "XX"; |
| break; |
| } |
| |
| /* Now figure out the family, the first two digits */ |
| switch ((chip_id >> 8) & 0xff) { |
| case 0: /* CN38XX, CN37XX or CN36XX */ |
| if (fus3.cn38xx.crip_512k) { |
| /* |
| * For some unknown reason, the 16 core one is |
| * called 37 instead of 36. |
| */ |
| if (num_cores >= 16) |
| family = "37"; |
| else |
| family = "36"; |
| } else |
| family = "38"; |
| /* |
| * This series of chips didn't follow the standard |
| * pass numbering. |
| */ |
| switch (chip_id & 0xf) { |
| case 0: |
| strcpy(pass, "1.X"); |
| break; |
| case 1: |
| strcpy(pass, "2.X"); |
| break; |
| case 3: |
| strcpy(pass, "3.X"); |
| break; |
| default: |
| strcpy(pass, "X.X"); |
| break; |
| } |
| break; |
| case 1: /* CN31XX or CN3020 */ |
| if ((chip_id & 0x10) || fus3.cn31xx.crip_128k) |
| family = "30"; |
| else |
| family = "31"; |
| /* |
| * This series of chips didn't follow the standard |
| * pass numbering. |
| */ |
| switch (chip_id & 0xf) { |
| case 0: |
| strcpy(pass, "1.0"); |
| break; |
| case 2: |
| strcpy(pass, "1.1"); |
| break; |
| default: |
| strcpy(pass, "X.X"); |
| break; |
| } |
| break; |
| case 2: /* CN3010 or CN3005 */ |
| family = "30"; |
| /* A chip with half cache is an 05 */ |
| if (fus3.cn30xx.crip_64k) |
| core_model = "05"; |
| /* |
| * This series of chips didn't follow the standard |
| * pass numbering. |
| */ |
| switch (chip_id & 0xf) { |
| case 0: |
| strcpy(pass, "1.0"); |
| break; |
| case 2: |
| strcpy(pass, "1.1"); |
| break; |
| default: |
| strcpy(pass, "X.X"); |
| break; |
| } |
| break; |
| case 3: /* CN58XX */ |
| family = "58"; |
| /* Special case. 4 core, half cache (CP with half cache) */ |
| if ((num_cores == 4) && fus3.cn58xx.crip_1024k && !strncmp(suffix, "CP", 2)) |
| core_model = "29"; |
| |
| /* Pass 1 uses different encodings for pass numbers */ |
| if ((chip_id & 0xFF) < 0x8) { |
| switch (chip_id & 0x3) { |
| case 0: |
| strcpy(pass, "1.0"); |
| break; |
| case 1: |
| strcpy(pass, "1.1"); |
| break; |
| case 3: |
| strcpy(pass, "1.2"); |
| break; |
| default: |
| strcpy(pass, "1.X"); |
| break; |
| } |
| } |
| break; |
| case 4: /* CN57XX, CN56XX, CN55XX, CN54XX */ |
| if (fus_dat2.cn56xx.raid_en) { |
| if (fus3.cn56xx.crip_1024k) |
| family = "55"; |
| else |
| family = "57"; |
| if (fus_dat2.cn56xx.nocrypto) |
| suffix = "SP"; |
| else |
| suffix = "SSP"; |
| } else { |
| if (fus_dat2.cn56xx.nocrypto) |
| suffix = "CP"; |
| else { |
| suffix = "NSP"; |
| if (fus_dat3.s.nozip) |
| suffix = "SCP"; |
| |
| if (fus_dat3.s.bar2_en) |
| suffix = "NSPB2"; |
| } |
| if (fus3.cn56xx.crip_1024k) |
| family = "54"; |
| else |
| family = "56"; |
| } |
| break; |
| case 6: /* CN50XX */ |
| family = "50"; |
| break; |
| case 7: /* CN52XX */ |
| if (fus3.cn52xx.crip_256k) |
| family = "51"; |
| else |
| family = "52"; |
| break; |
| case 0x93: /* CN61XX */ |
| family = "61"; |
| if (fus_dat2.cn61xx.nocrypto && fus_dat2.cn61xx.dorm_crypto) |
| suffix = "AP"; |
| if (fus_dat2.cn61xx.nocrypto) |
| suffix = "CP"; |
| else if (fus_dat2.cn61xx.dorm_crypto) |
| suffix = "DAP"; |
| else if (fus_dat3.cn61xx.nozip) |
| suffix = "SCP"; |
| break; |
| case 0x90: /* CN63XX */ |
| family = "63"; |
| if (fus_dat3.s.l2c_crip == 2) |
| family = "62"; |
| if (num_cores == 6) /* Other core counts match generic */ |
| core_model = "35"; |
| if (fus_dat2.cn63xx.nocrypto) |
| suffix = "CP"; |
| else if (fus_dat2.cn63xx.dorm_crypto) |
| suffix = "DAP"; |
| else if (fus_dat3.cn63xx.nozip) |
| suffix = "SCP"; |
| else |
| suffix = "AAP"; |
| break; |
| case 0x92: /* CN66XX */ |
| family = "66"; |
| if (num_cores == 6) /* Other core counts match generic */ |
| core_model = "35"; |
| if (fus_dat2.cn66xx.nocrypto && fus_dat2.cn66xx.dorm_crypto) |
| suffix = "AP"; |
| if (fus_dat2.cn66xx.nocrypto) |
| suffix = "CP"; |
| else if (fus_dat2.cn66xx.dorm_crypto) |
| suffix = "DAP"; |
| else if (fus_dat3.cn66xx.nozip) |
| suffix = "SCP"; |
| else |
| suffix = "AAP"; |
| break; |
| case 0x91: /* CN68XX */ |
| family = "68"; |
| if (fus_dat2.cn68xx.nocrypto && fus_dat3.cn68xx.nozip) |
| suffix = "CP"; |
| else if (fus_dat2.cn68xx.dorm_crypto) |
| suffix = "DAP"; |
| else if (fus_dat3.cn68xx.nozip) |
| suffix = "SCP"; |
| else if (fus_dat2.cn68xx.nocrypto) |
| suffix = "SP"; |
| else |
| suffix = "AAP"; |
| break; |
| default: |
| family = "XX"; |
| core_model = "XX"; |
| strcpy(pass, "X.X"); |
| suffix = "XXX"; |
| break; |
| } |
| |
| clock_mhz = octeon_get_clock_rate() / 1000000; |
| if (family[0] != '3') { |
| int fuse_base = 384 / 8; |
| if (family[0] == '6') |
| fuse_base = 832 / 8; |
| |
| /* Check for model in fuses, overrides normal decode */ |
| /* This is _not_ valid for Octeon CN3XXX models */ |
| fuse_data |= cvmx_fuse_read_byte(fuse_base + 3); |
| fuse_data = fuse_data << 8; |
| fuse_data |= cvmx_fuse_read_byte(fuse_base + 2); |
| fuse_data = fuse_data << 8; |
| fuse_data |= cvmx_fuse_read_byte(fuse_base + 1); |
| fuse_data = fuse_data << 8; |
| fuse_data |= cvmx_fuse_read_byte(fuse_base); |
| if (fuse_data & 0x7ffff) { |
| int model = fuse_data & 0x3fff; |
| int suffix = (fuse_data >> 14) & 0x1f; |
| if (suffix && model) { |
| /* Have both number and suffix in fuses, so both */ |
| sprintf(fuse_model, "%d%c", model, 'A' + suffix - 1); |
| core_model = ""; |
| family = fuse_model; |
| } else if (suffix && !model) { |
| /* Only have suffix, so add suffix to 'normal' model number */ |
| sprintf(fuse_model, "%s%c", core_model, 'A' + suffix - 1); |
| core_model = fuse_model; |
| } else { |
| /* Don't have suffix, so just use model from fuses */ |
| sprintf(fuse_model, "%d", model); |
| core_model = ""; |
| family = fuse_model; |
| } |
| } |
| } |
| sprintf(buffer, "CN%s%sp%s-%d-%s", family, core_model, pass, clock_mhz, suffix); |
| return buffer; |
| } |
| |
| /** |
| * Given the chip processor ID from COP0, this function returns a |
| * string representing the chip model number. The string is of the |
| * form CNXXXXpX.X-FREQ-SUFFIX. |
| * - XXXX = The chip model number |
| * - X.X = Chip pass number |
| * - FREQ = Current frequency in Mhz |
| * - SUFFIX = NSP, EXP, SCP, SSP, or CP |
| * |
| * @chip_id: Chip ID |
| * |
| * Returns Model string |
| */ |
| const char *__init octeon_model_get_string(uint32_t chip_id) |
| { |
| static char buffer[32]; |
| return octeon_model_get_string_buffer(chip_id, buffer); |
| } |