1000 lines · c
1// SPDX-License-Identifier: GPL-2.0+2//3// silicom-platform.c - Silicom MEC170x platform driver4//5// Copyright (C) 2023 Henry Shi <henrys@silicom-usa.com>6#include <linux/bitfield.h>7#include <linux/bits.h>8#include <linux/dmi.h>9#include <linux/hwmon.h>10#include <linux/init.h>11#include <linux/ioport.h>12#include <linux/io.h>13#include <linux/kernel.h>14#include <linux/kobject.h>15#include <linux/led-class-multicolor.h>16#include <linux/module.h>17#include <linux/mutex.h>18#include <linux/platform_device.h>19#include <linux/string.h>20#include <linux/sysfs.h>21#include <linux/units.h>22 23#include <linux/gpio/driver.h>24 25#define MEC_POWER_CYCLE_ADDR 0x2426#define MEC_EFUSE_LSB_ADDR 0x2827#define MEC_GPIO_IN_POS 0x0828#define MEC_IO_BASE 0x080029#define MEC_IO_LEN 0x830#define IO_REG_BANK 0x031#define DEFAULT_CHAN_LO 032#define DEFAULT_CHAN_HI 033#define DEFAULT_CHAN_LO_T 0xc34#define MEC_ADDR (MEC_IO_BASE + 0x02)35#define EC_ADDR_LSB MEC_ADDR36#define SILICOM_MEC_MAGIC 0x5a37 38#define MEC_PORT_CHANNEL_MASK GENMASK(2, 0)39#define MEC_PORT_DWORD_OFFSET GENMASK(31, 3)40#define MEC_DATA_OFFSET_MASK GENMASK(1, 0)41#define MEC_PORT_OFFSET_MASK GENMASK(7, 2)42 43#define MEC_TEMP_LOC GENMASK(31, 16)44#define MEC_VERSION_LOC GENMASK(15, 8)45#define MEC_VERSION_MAJOR GENMASK(15, 14)46#define MEC_VERSION_MINOR GENMASK(13, 8)47 48#define EC_ADDR_MSB (MEC_IO_BASE + 0x3)49#define MEC_DATA_OFFSET(offset) (MEC_IO_BASE + 0x04 + (offset))50 51#define OFFSET_BIT_TO_CHANNEL(off, bit) ((((off) + 0x014) << 3) | (bit))52#define CHANNEL_TO_OFFSET(chan) (((chan) >> 3) - 0x14)53 54static DEFINE_MUTEX(mec_io_mutex);55static unsigned int efuse_status;56static unsigned int mec_uc_version;57static unsigned int power_cycle;58 59static const struct hwmon_channel_info *silicom_fan_control_info[] = {60 HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL),61 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL),62 NULL63};64 65struct silicom_platform_info {66 int io_base;67 int io_len;68 struct led_classdev_mc *led_info;69 struct gpio_chip *gpiochip;70 u8 *gpio_channels;71 u16 ngpio;72};73 74static const char * const plat_0222_gpio_names[] = {75 "AUTOM0_SFP_TX_FAULT",76 "SLOT2_LED_OUT",77 "SIM_M2_SLOT2_B_DET",78 "SIM_M2_SLOT2_A_DET",79 "SLOT1_LED_OUT",80 "SIM_M2_SLOT1_B_DET",81 "SIM_M2_SLOT1_A_DET",82 "SLOT0_LED_OUT",83 "WAN_SFP0_RX_LOS",84 "WAN_SFP0_PRSNT_N",85 "WAN_SFP0_TX_FAULT",86 "AUTOM1_SFP_RX_LOS",87 "AUTOM1_SFP_PRSNT_N",88 "AUTOM1_SFP_TX_FAULT",89 "AUTOM0_SFP_RX_LOS",90 "AUTOM0_SFP_PRSNT_N",91 "WAN_SFP1_RX_LOS",92 "WAN_SFP1_PRSNT_N",93 "WAN_SFP1_TX_FAULT",94 "SIM_M2_SLOT1_MUX_SEL",95 "W_DISABLE_M2_SLOT1_N",96 "W_DISABLE_MPCIE_SLOT0_N",97 "W_DISABLE_M2_SLOT0_N",98 "BT_COMMAND_MODE",99 "WAN_SFP1_TX_DISABLE",100 "WAN_SFP0_TX_DISABLE",101 "AUTOM1_SFP_TX_DISABLE",102 "AUTOM0_SFP_TX_DISABLE",103 "SIM_M2_SLOT2_MUX_SEL",104 "W_DISABLE_M2_SLOT2_N",105 "RST_CTL_M2_SLOT_1_N",106 "RST_CTL_M2_SLOT_2_N",107 "PM_USB_PWR_EN_BOT",108 "PM_USB_PWR_EN_TOP",109};110 111static u8 plat_0222_gpio_channels[] = {112 OFFSET_BIT_TO_CHANNEL(0x00, 0),113 OFFSET_BIT_TO_CHANNEL(0x00, 1),114 OFFSET_BIT_TO_CHANNEL(0x00, 2),115 OFFSET_BIT_TO_CHANNEL(0x00, 3),116 OFFSET_BIT_TO_CHANNEL(0x00, 4),117 OFFSET_BIT_TO_CHANNEL(0x00, 5),118 OFFSET_BIT_TO_CHANNEL(0x00, 6),119 OFFSET_BIT_TO_CHANNEL(0x00, 7),120 OFFSET_BIT_TO_CHANNEL(0x01, 0),121 OFFSET_BIT_TO_CHANNEL(0x01, 1),122 OFFSET_BIT_TO_CHANNEL(0x01, 2),123 OFFSET_BIT_TO_CHANNEL(0x01, 3),124 OFFSET_BIT_TO_CHANNEL(0x01, 4),125 OFFSET_BIT_TO_CHANNEL(0x01, 5),126 OFFSET_BIT_TO_CHANNEL(0x01, 6),127 OFFSET_BIT_TO_CHANNEL(0x01, 7),128 OFFSET_BIT_TO_CHANNEL(0x02, 0),129 OFFSET_BIT_TO_CHANNEL(0x02, 1),130 OFFSET_BIT_TO_CHANNEL(0x02, 2),131 OFFSET_BIT_TO_CHANNEL(0x09, 0),132 OFFSET_BIT_TO_CHANNEL(0x09, 1),133 OFFSET_BIT_TO_CHANNEL(0x09, 2),134 OFFSET_BIT_TO_CHANNEL(0x09, 3),135 OFFSET_BIT_TO_CHANNEL(0x0a, 0),136 OFFSET_BIT_TO_CHANNEL(0x0a, 1),137 OFFSET_BIT_TO_CHANNEL(0x0a, 2),138 OFFSET_BIT_TO_CHANNEL(0x0a, 3),139 OFFSET_BIT_TO_CHANNEL(0x0a, 4),140 OFFSET_BIT_TO_CHANNEL(0x0a, 5),141 OFFSET_BIT_TO_CHANNEL(0x0a, 6),142 OFFSET_BIT_TO_CHANNEL(0x0b, 0),143 OFFSET_BIT_TO_CHANNEL(0x0b, 1),144 OFFSET_BIT_TO_CHANNEL(0x0b, 2),145 OFFSET_BIT_TO_CHANNEL(0x0b, 3),146};147 148static struct platform_device *silicom_platform_dev;149static struct led_classdev_mc *silicom_led_info __initdata;150static struct gpio_chip *silicom_gpiochip __initdata;151static u8 *silicom_gpio_channels __initdata;152 153static int silicom_mec_port_get(unsigned int offset)154{155 unsigned short mec_data_addr;156 unsigned short mec_port_addr;157 u8 reg;158 159 mec_data_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, offset) & MEC_DATA_OFFSET_MASK;160 mec_port_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, offset) & MEC_PORT_OFFSET_MASK;161 162 mutex_lock(&mec_io_mutex);163 outb(mec_port_addr, MEC_ADDR);164 reg = inb(MEC_DATA_OFFSET(mec_data_addr));165 mutex_unlock(&mec_io_mutex);166 167 return (reg >> (offset & MEC_PORT_CHANNEL_MASK)) & 0x01;168}169 170static enum led_brightness silicom_mec_led_get(int channel)171{172 /* Outputs are active low */173 return silicom_mec_port_get(channel) ? LED_OFF : LED_ON;174}175 176static void silicom_mec_port_set(int channel, int on)177{178 179 unsigned short mec_data_addr;180 unsigned short mec_port_addr;181 u8 reg;182 183 mec_data_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, channel) & MEC_DATA_OFFSET_MASK;184 mec_port_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, channel) & MEC_PORT_OFFSET_MASK;185 186 mutex_lock(&mec_io_mutex);187 outb(mec_port_addr, MEC_ADDR);188 reg = inb(MEC_DATA_OFFSET(mec_data_addr));189 /* Outputs are active low, so clear the bit for on, or set it for off */190 if (on)191 reg &= ~(1 << (channel & MEC_PORT_CHANNEL_MASK));192 else193 reg |= 1 << (channel & MEC_PORT_CHANNEL_MASK);194 outb(reg, MEC_DATA_OFFSET(mec_data_addr));195 mutex_unlock(&mec_io_mutex);196}197 198static enum led_brightness silicom_mec_led_mc_brightness_get(struct led_classdev *led_cdev)199{200 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);201 enum led_brightness brightness = LED_OFF;202 int i;203 204 for (i = 0; i < mc_cdev->num_colors; i++) {205 mc_cdev->subled_info[i].brightness =206 silicom_mec_led_get(mc_cdev->subled_info[i].channel);207 /* Mark the overall brightness as LED_ON if any of the subleds are on */208 if (mc_cdev->subled_info[i].brightness != LED_OFF)209 brightness = LED_ON;210 }211 212 return brightness;213}214 215static void silicom_mec_led_mc_brightness_set(struct led_classdev *led_cdev,216 enum led_brightness brightness)217{218 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);219 int i;220 221 led_mc_calc_color_components(mc_cdev, brightness);222 for (i = 0; i < mc_cdev->num_colors; i++) {223 silicom_mec_port_set(mc_cdev->subled_info[i].channel,224 mc_cdev->subled_info[i].brightness);225 }226}227 228static int silicom_gpio_get_direction(struct gpio_chip *gc,229 unsigned int offset)230{231 u8 *channels = gpiochip_get_data(gc);232 233 /* Input registers have offsets between [0x00, 0x07] */234 if (CHANNEL_TO_OFFSET(channels[offset]) < MEC_GPIO_IN_POS)235 return GPIO_LINE_DIRECTION_IN;236 237 return GPIO_LINE_DIRECTION_OUT;238}239 240static int silicom_gpio_direction_input(struct gpio_chip *gc,241 unsigned int offset)242{243 int direction = silicom_gpio_get_direction(gc, offset);244 245 return direction == GPIO_LINE_DIRECTION_IN ? 0 : -EINVAL;246}247 248static void silicom_gpio_set(struct gpio_chip *gc,249 unsigned int offset,250 int value)251{252 int direction = silicom_gpio_get_direction(gc, offset);253 u8 *channels = gpiochip_get_data(gc);254 int channel = channels[offset];255 256 if (direction == GPIO_LINE_DIRECTION_IN)257 return;258 259 silicom_mec_port_set(channel, !value);260}261 262static int silicom_gpio_direction_output(struct gpio_chip *gc,263 unsigned int offset,264 int value)265{266 int direction = silicom_gpio_get_direction(gc, offset);267 268 if (direction == GPIO_LINE_DIRECTION_IN)269 return -EINVAL;270 271 silicom_gpio_set(gc, offset, value);272 273 return 0;274}275 276static int silicom_gpio_get(struct gpio_chip *gc, unsigned int offset)277{278 u8 *channels = gpiochip_get_data(gc);279 int channel = channels[offset];280 281 return silicom_mec_port_get(channel);282}283 284static struct mc_subled plat_0222_wan_mc_subled_info[] __initdata = {285 {286 .color_index = LED_COLOR_ID_WHITE,287 .brightness = 1,288 .intensity = 0,289 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 7),290 },291 {292 .color_index = LED_COLOR_ID_YELLOW,293 .brightness = 1,294 .intensity = 0,295 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 6),296 },297 {298 .color_index = LED_COLOR_ID_RED,299 .brightness = 1,300 .intensity = 0,301 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 5),302 },303};304 305static struct mc_subled plat_0222_sys_mc_subled_info[] __initdata = {306 {307 .color_index = LED_COLOR_ID_WHITE,308 .brightness = 1,309 .intensity = 0,310 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 4),311 },312 {313 .color_index = LED_COLOR_ID_AMBER,314 .brightness = 1,315 .intensity = 0,316 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 3),317 },318 {319 .color_index = LED_COLOR_ID_RED,320 .brightness = 1,321 .intensity = 0,322 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 2),323 },324};325 326static struct mc_subled plat_0222_stat1_mc_subled_info[] __initdata = {327 {328 .color_index = LED_COLOR_ID_RED,329 .brightness = 1,330 .intensity = 0,331 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 1),332 },333 {334 .color_index = LED_COLOR_ID_GREEN,335 .brightness = 1,336 .intensity = 0,337 .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 0),338 },339 {340 .color_index = LED_COLOR_ID_BLUE,341 .brightness = 1,342 .intensity = 0,343 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 7),344 },345 {346 .color_index = LED_COLOR_ID_YELLOW,347 .brightness = 1,348 .intensity = 0,349 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 6),350 },351};352 353static struct mc_subled plat_0222_stat2_mc_subled_info[] __initdata = {354 {355 .color_index = LED_COLOR_ID_RED,356 .brightness = 1,357 .intensity = 0,358 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 5),359 },360 {361 .color_index = LED_COLOR_ID_GREEN,362 .brightness = 1,363 .intensity = 0,364 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 4),365 },366 {367 .color_index = LED_COLOR_ID_BLUE,368 .brightness = 1,369 .intensity = 0,370 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 3),371 },372 {373 .color_index = LED_COLOR_ID_YELLOW,374 .brightness = 1,375 .intensity = 0,376 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 2),377 },378};379 380static struct mc_subled plat_0222_stat3_mc_subled_info[] __initdata = {381 {382 .color_index = LED_COLOR_ID_RED,383 .brightness = 1,384 .intensity = 0,385 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 1),386 },387 {388 .color_index = LED_COLOR_ID_GREEN,389 .brightness = 1,390 .intensity = 0,391 .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 0),392 },393 {394 .color_index = LED_COLOR_ID_BLUE,395 .brightness = 1,396 .intensity = 0,397 .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 1),398 },399 {400 .color_index = LED_COLOR_ID_YELLOW,401 .brightness = 1,402 .intensity = 0,403 .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 0),404 },405};406 407static struct led_classdev_mc plat_0222_mc_led_info[] __initdata = {408 {409 .led_cdev = {410 .name = "platled::wan",411 .brightness = 0,412 .max_brightness = 1,413 .brightness_set = silicom_mec_led_mc_brightness_set,414 .brightness_get = silicom_mec_led_mc_brightness_get,415 },416 .num_colors = ARRAY_SIZE(plat_0222_wan_mc_subled_info),417 .subled_info = plat_0222_wan_mc_subled_info,418 },419 {420 .led_cdev = {421 .name = "platled::sys",422 .brightness = 0,423 .max_brightness = 1,424 .brightness_set = silicom_mec_led_mc_brightness_set,425 .brightness_get = silicom_mec_led_mc_brightness_get,426 },427 .num_colors = ARRAY_SIZE(plat_0222_sys_mc_subled_info),428 .subled_info = plat_0222_sys_mc_subled_info,429 },430 {431 .led_cdev = {432 .name = "platled::stat1",433 .brightness = 0,434 .max_brightness = 1,435 .brightness_set = silicom_mec_led_mc_brightness_set,436 .brightness_get = silicom_mec_led_mc_brightness_get,437 },438 .num_colors = ARRAY_SIZE(plat_0222_stat1_mc_subled_info),439 .subled_info = plat_0222_stat1_mc_subled_info,440 },441 {442 .led_cdev = {443 .name = "platled::stat2",444 .brightness = 0,445 .max_brightness = 1,446 .brightness_set = silicom_mec_led_mc_brightness_set,447 .brightness_get = silicom_mec_led_mc_brightness_get,448 },449 .num_colors = ARRAY_SIZE(plat_0222_stat2_mc_subled_info),450 .subled_info = plat_0222_stat2_mc_subled_info,451 },452 {453 .led_cdev = {454 .name = "platled::stat3",455 .brightness = 0,456 .max_brightness = 1,457 .brightness_set = silicom_mec_led_mc_brightness_set,458 .brightness_get = silicom_mec_led_mc_brightness_get,459 },460 .num_colors = ARRAY_SIZE(plat_0222_stat3_mc_subled_info),461 .subled_info = plat_0222_stat3_mc_subled_info,462 },463 { },464};465 466static struct gpio_chip silicom_gpio_chip = {467 .label = "silicom-gpio",468 .get_direction = silicom_gpio_get_direction,469 .direction_input = silicom_gpio_direction_input,470 .direction_output = silicom_gpio_direction_output,471 .get = silicom_gpio_get,472 .set = silicom_gpio_set,473 .base = -1,474 .ngpio = ARRAY_SIZE(plat_0222_gpio_channels),475 .names = plat_0222_gpio_names,476 /*477 * We're using a mutex to protect the indirect access, so we can sleep478 * if the lock blocks479 */480 .can_sleep = true,481};482 483static struct silicom_platform_info silicom_plat_0222_cordoba_info __initdata = {484 .io_base = MEC_IO_BASE,485 .io_len = MEC_IO_LEN,486 .led_info = plat_0222_mc_led_info,487 .gpiochip = &silicom_gpio_chip,488 .gpio_channels = plat_0222_gpio_channels,489 /*490 * The original generic cordoba does not have the last 4 outputs of the491 * plat_0222 variant, the rest are the same, so use the same longer list,492 * but ignore the last entries here493 */494 .ngpio = ARRAY_SIZE(plat_0222_gpio_channels),495 496};497 498static struct mc_subled cordoba_fp_left_mc_subled_info[] __initdata = {499 {500 .color_index = LED_COLOR_ID_RED,501 .brightness = 1,502 .intensity = 0,503 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 6),504 },505 {506 .color_index = LED_COLOR_ID_GREEN,507 .brightness = 1,508 .intensity = 0,509 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 5),510 },511 {512 .color_index = LED_COLOR_ID_BLUE,513 .brightness = 1,514 .intensity = 0,515 .channel = OFFSET_BIT_TO_CHANNEL(0x09, 7),516 },517 {518 .color_index = LED_COLOR_ID_AMBER,519 .brightness = 1,520 .intensity = 0,521 .channel = OFFSET_BIT_TO_CHANNEL(0x09, 4),522 },523};524 525static struct mc_subled cordoba_fp_center_mc_subled_info[] __initdata = {526 {527 .color_index = LED_COLOR_ID_RED,528 .brightness = 1,529 .intensity = 0,530 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 7),531 },532 {533 .color_index = LED_COLOR_ID_GREEN,534 .brightness = 1,535 .intensity = 0,536 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 4),537 },538 {539 .color_index = LED_COLOR_ID_BLUE,540 .brightness = 1,541 .intensity = 0,542 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 3),543 },544 {545 .color_index = LED_COLOR_ID_AMBER,546 .brightness = 1,547 .intensity = 0,548 .channel = OFFSET_BIT_TO_CHANNEL(0x09, 6),549 },550};551 552static struct mc_subled cordoba_fp_right_mc_subled_info[] __initdata = {553 {554 .color_index = LED_COLOR_ID_RED,555 .brightness = 1,556 .intensity = 0,557 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 2),558 },559 {560 .color_index = LED_COLOR_ID_GREEN,561 .brightness = 1,562 .intensity = 0,563 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 1),564 },565 {566 .color_index = LED_COLOR_ID_BLUE,567 .brightness = 1,568 .intensity = 0,569 .channel = OFFSET_BIT_TO_CHANNEL(0x08, 0),570 },571 {572 .color_index = LED_COLOR_ID_AMBER,573 .brightness = 1,574 .intensity = 0,575 .channel = OFFSET_BIT_TO_CHANNEL(0x09, 5),576 },577};578 579static struct led_classdev_mc cordoba_mc_led_info[] __initdata = {580 {581 .led_cdev = {582 .name = "platled::fp_left",583 .brightness = 0,584 .max_brightness = 1,585 .brightness_set = silicom_mec_led_mc_brightness_set,586 .brightness_get = silicom_mec_led_mc_brightness_get,587 },588 .num_colors = ARRAY_SIZE(cordoba_fp_left_mc_subled_info),589 .subled_info = cordoba_fp_left_mc_subled_info,590 },591 {592 .led_cdev = {593 .name = "platled::fp_center",594 .brightness = 0,595 .max_brightness = 1,596 .brightness_set = silicom_mec_led_mc_brightness_set,597 .brightness_get = silicom_mec_led_mc_brightness_get,598 },599 .num_colors = ARRAY_SIZE(cordoba_fp_center_mc_subled_info),600 .subled_info = cordoba_fp_center_mc_subled_info,601 },602 {603 .led_cdev = {604 .name = "platled::fp_right",605 .brightness = 0,606 .max_brightness = 1,607 .brightness_set = silicom_mec_led_mc_brightness_set,608 .brightness_get = silicom_mec_led_mc_brightness_get,609 },610 .num_colors = ARRAY_SIZE(cordoba_fp_right_mc_subled_info),611 .subled_info = cordoba_fp_right_mc_subled_info,612 },613 { },614};615 616static struct silicom_platform_info silicom_generic_cordoba_info __initdata = {617 .io_base = MEC_IO_BASE,618 .io_len = MEC_IO_LEN,619 .led_info = cordoba_mc_led_info,620 .gpiochip = &silicom_gpio_chip,621 .gpio_channels = plat_0222_gpio_channels,622 .ngpio = ARRAY_SIZE(plat_0222_gpio_channels),623};624 625/*626 * sysfs interface627 */628static ssize_t efuse_status_show(struct device *dev,629 struct device_attribute *attr,630 char *buf)631{632 u32 reg;633 634 mutex_lock(&mec_io_mutex);635 /* Select memory region */636 outb(IO_REG_BANK, EC_ADDR_MSB);637 outb(MEC_EFUSE_LSB_ADDR, EC_ADDR_LSB);638 639 /* Get current data from the address */640 reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO));641 mutex_unlock(&mec_io_mutex);642 643 efuse_status = reg & 0x1;644 645 return sysfs_emit(buf, "%u\n", efuse_status);646}647static DEVICE_ATTR_RO(efuse_status);648 649static ssize_t uc_version_show(struct device *dev,650 struct device_attribute *attr,651 char *buf)652{653 int uc_version;654 u32 reg;655 656 mutex_lock(&mec_io_mutex);657 outb(IO_REG_BANK, EC_ADDR_MSB);658 outb(DEFAULT_CHAN_LO, EC_ADDR_LSB);659 660 reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO));661 mutex_unlock(&mec_io_mutex);662 uc_version = FIELD_GET(MEC_VERSION_LOC, reg);663 if (uc_version >= 192)664 return -EINVAL;665 666 uc_version = FIELD_GET(MEC_VERSION_MAJOR, reg) * 100 +667 FIELD_GET(MEC_VERSION_MINOR, reg);668 669 mec_uc_version = uc_version;670 671 return sysfs_emit(buf, "%u\n", mec_uc_version);672}673static DEVICE_ATTR_RO(uc_version);674 675static ssize_t power_cycle_show(struct device *dev,676 struct device_attribute *attr,677 char *buf)678{679 return sysfs_emit(buf, "%u\n", power_cycle);680}681 682static void powercycle_uc(void)683{684 /* Select memory region */685 outb(IO_REG_BANK, EC_ADDR_MSB);686 outb(MEC_POWER_CYCLE_ADDR, EC_ADDR_LSB);687 688 /* Set to 1 for current data from the address */689 outb(1, MEC_DATA_OFFSET(DEFAULT_CHAN_LO));690}691 692static ssize_t power_cycle_store(struct device *dev,693 struct device_attribute *attr,694 const char *buf, size_t count)695{696 int rc;697 unsigned int power_cycle_cmd;698 699 rc = kstrtou32(buf, 0, &power_cycle_cmd);700 if (rc)701 return -EINVAL;702 703 if (power_cycle_cmd > 0) {704 mutex_lock(&mec_io_mutex);705 power_cycle = power_cycle_cmd;706 powercycle_uc();707 mutex_unlock(&mec_io_mutex);708 }709 710 return count;711}712static DEVICE_ATTR_RW(power_cycle);713 714static struct attribute *silicom_attrs[] = {715 &dev_attr_efuse_status.attr,716 &dev_attr_uc_version.attr,717 &dev_attr_power_cycle.attr,718 NULL,719};720ATTRIBUTE_GROUPS(silicom);721 722static struct platform_driver silicom_platform_driver = {723 .driver = {724 .name = "silicom-platform",725 .dev_groups = silicom_groups,726 },727};728 729static int __init silicom_mc_leds_register(struct device *dev,730 const struct led_classdev_mc *mc_leds)731{732 int size = sizeof(struct mc_subled);733 struct led_classdev_mc *led;734 int i, err;735 736 for (i = 0; mc_leds[i].led_cdev.name; i++) {737 738 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);739 if (!led)740 return -ENOMEM;741 memcpy(led, &mc_leds[i], sizeof(*led));742 743 led->subled_info = devm_kzalloc(dev, led->num_colors * size, GFP_KERNEL);744 if (!led->subled_info)745 return -ENOMEM;746 memcpy(led->subled_info, mc_leds[i].subled_info, led->num_colors * size);747 748 err = devm_led_classdev_multicolor_register(dev, led);749 if (err)750 return err;751 }752 753 return 0;754}755 756static u32 rpm_get(void)757{758 u32 reg;759 760 mutex_lock(&mec_io_mutex);761 /* Select memory region */762 outb(IO_REG_BANK, EC_ADDR_MSB);763 outb(DEFAULT_CHAN_LO_T, EC_ADDR_LSB);764 reg = inw(MEC_DATA_OFFSET(DEFAULT_CHAN_LO));765 mutex_unlock(&mec_io_mutex);766 767 return reg;768}769 770static u32 temp_get(void)771{772 u32 reg;773 774 mutex_lock(&mec_io_mutex);775 /* Select memory region */776 outb(IO_REG_BANK, EC_ADDR_MSB);777 outb(DEFAULT_CHAN_LO_T, EC_ADDR_LSB);778 reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO));779 mutex_unlock(&mec_io_mutex);780 781 return FIELD_GET(MEC_TEMP_LOC, reg) * 100;782}783 784static umode_t silicom_fan_control_fan_is_visible(const u32 attr)785{786 switch (attr) {787 case hwmon_fan_input:788 case hwmon_fan_label:789 return 0444;790 default:791 return 0;792 }793}794 795static umode_t silicom_fan_control_temp_is_visible(const u32 attr)796{797 switch (attr) {798 case hwmon_temp_input:799 case hwmon_temp_label:800 return 0444;801 default:802 return 0;803 }804}805 806static int silicom_fan_control_read_fan(struct device *dev, u32 attr, long *val)807{808 switch (attr) {809 case hwmon_fan_input:810 *val = rpm_get();811 return 0;812 default:813 return -EOPNOTSUPP;814 }815}816 817static int silicom_fan_control_read_temp(struct device *dev, u32 attr, long *val)818{819 switch (attr) {820 case hwmon_temp_input:821 *val = temp_get();822 return 0;823 default:824 return -EOPNOTSUPP;825 }826}827 828static umode_t silicom_fan_control_is_visible(const void *data,829 enum hwmon_sensor_types type,830 u32 attr, int channel)831{832 switch (type) {833 case hwmon_fan:834 return silicom_fan_control_fan_is_visible(attr);835 case hwmon_temp:836 return silicom_fan_control_temp_is_visible(attr);837 default:838 return 0;839 }840}841 842static int silicom_fan_control_read(struct device *dev,843 enum hwmon_sensor_types type,844 u32 attr, int channel,845 long *val)846{847 switch (type) {848 case hwmon_fan:849 return silicom_fan_control_read_fan(dev, attr, val);850 case hwmon_temp:851 return silicom_fan_control_read_temp(dev, attr, val);852 default:853 return -EOPNOTSUPP;854 }855}856 857static int silicom_fan_control_read_labels(struct device *dev,858 enum hwmon_sensor_types type,859 u32 attr, int channel,860 const char **str)861{862 switch (type) {863 case hwmon_fan:864 *str = "Silicom_platform: Fan Speed";865 return 0;866 case hwmon_temp:867 *str = "Silicom_platform: Thermostat Sensor";868 return 0;869 default:870 return -EOPNOTSUPP;871 }872}873 874static const struct hwmon_ops silicom_fan_control_hwmon_ops = {875 .is_visible = silicom_fan_control_is_visible,876 .read = silicom_fan_control_read,877 .read_string = silicom_fan_control_read_labels,878};879 880static const struct hwmon_chip_info silicom_chip_info = {881 .ops = &silicom_fan_control_hwmon_ops,882 .info = silicom_fan_control_info,883};884 885static int __init silicom_platform_probe(struct platform_device *device)886{887 struct device *hwmon_dev;888 u8 magic, ver;889 int err;890 891 if (!devm_request_region(&device->dev, MEC_IO_BASE, MEC_IO_LEN, "mec")) {892 dev_err(&device->dev, "couldn't reserve MEC io ports\n");893 return -EBUSY;894 }895 896 /* Sanity check magic number read for EC */897 outb(IO_REG_BANK, MEC_ADDR);898 magic = inb(MEC_DATA_OFFSET(DEFAULT_CHAN_LO));899 ver = inb(MEC_DATA_OFFSET(DEFAULT_CHAN_HI));900 dev_dbg(&device->dev, "EC magic 0x%02x, version 0x%02x\n", magic, ver);901 902 if (magic != SILICOM_MEC_MAGIC) {903 dev_err(&device->dev, "Bad EC magic 0x%02x!\n", magic);904 return -ENODEV;905 }906 907 err = silicom_mc_leds_register(&device->dev, silicom_led_info);908 if (err) {909 dev_err(&device->dev, "Failed to register LEDs\n");910 return err;911 }912 913 err = devm_gpiochip_add_data(&device->dev, silicom_gpiochip,914 silicom_gpio_channels);915 if (err) {916 dev_err(&device->dev, "Failed to register gpiochip: %d\n", err);917 return err;918 }919 920 hwmon_dev = devm_hwmon_device_register_with_info(&device->dev, "silicom_fan", NULL,921 &silicom_chip_info, NULL);922 err = PTR_ERR_OR_ZERO(hwmon_dev);923 if (err) {924 dev_err(&device->dev, "Failed to register hwmon_dev: %d\n", err);925 return err;926 }927 928 return err;929}930 931static int __init silicom_platform_info_init(const struct dmi_system_id *id)932{933 struct silicom_platform_info *info = id->driver_data;934 935 silicom_led_info = info->led_info;936 silicom_gpio_channels = info->gpio_channels;937 silicom_gpiochip = info->gpiochip;938 silicom_gpiochip->ngpio = info->ngpio;939 940 return 1;941}942 943static const struct dmi_system_id silicom_dmi_ids[] __initconst = {944 {945 .callback = silicom_platform_info_init,946 .ident = "Silicom Cordoba (Generic)",947 .matches = {948 DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"),949 DMI_MATCH(DMI_BOARD_NAME, "80300-0214-G"),950 },951 .driver_data = &silicom_generic_cordoba_info,952 },953 {954 .callback = silicom_platform_info_init,955 .ident = "Silicom Cordoba (Generic)",956 .matches = {957 DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"),958 DMI_MATCH(DMI_BOARD_NAME, "80500-0214-G"),959 },960 .driver_data = &silicom_generic_cordoba_info,961 },962 {963 .callback = silicom_platform_info_init,964 .ident = "Silicom Cordoba (plat_0222)",965 .matches = {966 DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"),967 DMI_MATCH(DMI_BOARD_NAME, "80300-0222-G"),968 },969 .driver_data = &silicom_plat_0222_cordoba_info,970 },971 { },972};973MODULE_DEVICE_TABLE(dmi, silicom_dmi_ids);974 975static int __init silicom_platform_init(void)976{977 if (!dmi_check_system(silicom_dmi_ids)) {978 pr_err("No DMI match for this platform\n");979 return -ENODEV;980 }981 silicom_platform_dev = platform_create_bundle(&silicom_platform_driver,982 silicom_platform_probe,983 NULL, 0, NULL, 0);984 985 return PTR_ERR_OR_ZERO(silicom_platform_dev);986}987 988static void __exit silicom_platform_exit(void)989{990 platform_device_unregister(silicom_platform_dev);991 platform_driver_unregister(&silicom_platform_driver);992}993 994module_init(silicom_platform_init);995module_exit(silicom_platform_exit);996 997MODULE_LICENSE("GPL");998MODULE_AUTHOR("Henry Shi <henrys@silicom-usa.com>");999MODULE_DESCRIPTION("Platform driver for Silicom network appliances");1000