// SPDX-License-Identifier: GPL-2.0+ // // silicom-platform.c - Silicom MEC170x platform driver // // Copyright (C) 2023 Henry Shi #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MEC_POWER_CYCLE_ADDR 0x24 #define MEC_EFUSE_LSB_ADDR 0x28 #define MEC_GPIO_IN_POS 0x08 #define MEC_IO_BASE 0x0800 #define MEC_IO_LEN 0x8 #define IO_REG_BANK 0x0 #define DEFAULT_CHAN_LO 0 #define DEFAULT_CHAN_HI 0 #define DEFAULT_CHAN_LO_T 0xc #define MEC_ADDR (MEC_IO_BASE + 0x02) #define EC_ADDR_LSB MEC_ADDR #define SILICOM_MEC_MAGIC 0x5a #define MEC_PORT_CHANNEL_MASK GENMASK(2, 0) #define MEC_PORT_DWORD_OFFSET GENMASK(31, 3) #define MEC_DATA_OFFSET_MASK GENMASK(1, 0) #define MEC_PORT_OFFSET_MASK GENMASK(7, 2) #define MEC_TEMP_LOC GENMASK(31, 16) #define MEC_VERSION_LOC GENMASK(15, 8) #define MEC_VERSION_MAJOR GENMASK(15, 14) #define MEC_VERSION_MINOR GENMASK(13, 8) #define EC_ADDR_MSB (MEC_IO_BASE + 0x3) #define MEC_DATA_OFFSET(offset) (MEC_IO_BASE + 0x04 + (offset)) #define OFFSET_BIT_TO_CHANNEL(off, bit) ((((off) + 0x014) << 3) | (bit)) #define CHANNEL_TO_OFFSET(chan) (((chan) >> 3) - 0x14) static DEFINE_MUTEX(mec_io_mutex); static unsigned int efuse_status; static unsigned int mec_uc_version; static unsigned int power_cycle; static const struct hwmon_channel_info *silicom_fan_control_info[] = { HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), NULL }; struct silicom_platform_info { int io_base; int io_len; struct led_classdev_mc *led_info; struct gpio_chip *gpiochip; u8 *gpio_channels; u16 ngpio; }; static const char * const plat_0222_gpio_names[] = { "AUTOM0_SFP_TX_FAULT", "SLOT2_LED_OUT", "SIM_M2_SLOT2_B_DET", "SIM_M2_SLOT2_A_DET", "SLOT1_LED_OUT", "SIM_M2_SLOT1_B_DET", "SIM_M2_SLOT1_A_DET", "SLOT0_LED_OUT", "WAN_SFP0_RX_LOS", "WAN_SFP0_PRSNT_N", "WAN_SFP0_TX_FAULT", "AUTOM1_SFP_RX_LOS", "AUTOM1_SFP_PRSNT_N", "AUTOM1_SFP_TX_FAULT", "AUTOM0_SFP_RX_LOS", "AUTOM0_SFP_PRSNT_N", "WAN_SFP1_RX_LOS", "WAN_SFP1_PRSNT_N", "WAN_SFP1_TX_FAULT", "SIM_M2_SLOT1_MUX_SEL", "W_DISABLE_M2_SLOT1_N", "W_DISABLE_MPCIE_SLOT0_N", "W_DISABLE_M2_SLOT0_N", "BT_COMMAND_MODE", "WAN_SFP1_TX_DISABLE", "WAN_SFP0_TX_DISABLE", "AUTOM1_SFP_TX_DISABLE", "AUTOM0_SFP_TX_DISABLE", "SIM_M2_SLOT2_MUX_SEL", "W_DISABLE_M2_SLOT2_N", "RST_CTL_M2_SLOT_1_N", "RST_CTL_M2_SLOT_2_N", "PM_USB_PWR_EN_BOT", "PM_USB_PWR_EN_TOP", }; static u8 plat_0222_gpio_channels[] = { OFFSET_BIT_TO_CHANNEL(0x00, 0), OFFSET_BIT_TO_CHANNEL(0x00, 1), OFFSET_BIT_TO_CHANNEL(0x00, 2), OFFSET_BIT_TO_CHANNEL(0x00, 3), OFFSET_BIT_TO_CHANNEL(0x00, 4), OFFSET_BIT_TO_CHANNEL(0x00, 5), OFFSET_BIT_TO_CHANNEL(0x00, 6), OFFSET_BIT_TO_CHANNEL(0x00, 7), OFFSET_BIT_TO_CHANNEL(0x01, 0), OFFSET_BIT_TO_CHANNEL(0x01, 1), OFFSET_BIT_TO_CHANNEL(0x01, 2), OFFSET_BIT_TO_CHANNEL(0x01, 3), OFFSET_BIT_TO_CHANNEL(0x01, 4), OFFSET_BIT_TO_CHANNEL(0x01, 5), OFFSET_BIT_TO_CHANNEL(0x01, 6), OFFSET_BIT_TO_CHANNEL(0x01, 7), OFFSET_BIT_TO_CHANNEL(0x02, 0), OFFSET_BIT_TO_CHANNEL(0x02, 1), OFFSET_BIT_TO_CHANNEL(0x02, 2), OFFSET_BIT_TO_CHANNEL(0x09, 0), OFFSET_BIT_TO_CHANNEL(0x09, 1), OFFSET_BIT_TO_CHANNEL(0x09, 2), OFFSET_BIT_TO_CHANNEL(0x09, 3), OFFSET_BIT_TO_CHANNEL(0x0a, 0), OFFSET_BIT_TO_CHANNEL(0x0a, 1), OFFSET_BIT_TO_CHANNEL(0x0a, 2), OFFSET_BIT_TO_CHANNEL(0x0a, 3), OFFSET_BIT_TO_CHANNEL(0x0a, 4), OFFSET_BIT_TO_CHANNEL(0x0a, 5), OFFSET_BIT_TO_CHANNEL(0x0a, 6), OFFSET_BIT_TO_CHANNEL(0x0b, 0), OFFSET_BIT_TO_CHANNEL(0x0b, 1), OFFSET_BIT_TO_CHANNEL(0x0b, 2), OFFSET_BIT_TO_CHANNEL(0x0b, 3), }; static struct platform_device *silicom_platform_dev; static struct led_classdev_mc *silicom_led_info __initdata; static struct gpio_chip *silicom_gpiochip __initdata; static u8 *silicom_gpio_channels __initdata; static int silicom_mec_port_get(unsigned int offset) { unsigned short mec_data_addr; unsigned short mec_port_addr; u8 reg; mec_data_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, offset) & MEC_DATA_OFFSET_MASK; mec_port_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, offset) & MEC_PORT_OFFSET_MASK; mutex_lock(&mec_io_mutex); outb(mec_port_addr, MEC_ADDR); reg = inb(MEC_DATA_OFFSET(mec_data_addr)); mutex_unlock(&mec_io_mutex); return (reg >> (offset & MEC_PORT_CHANNEL_MASK)) & 0x01; } static enum led_brightness silicom_mec_led_get(int channel) { /* Outputs are active low */ return silicom_mec_port_get(channel) ? LED_OFF : LED_ON; } static void silicom_mec_port_set(int channel, int on) { unsigned short mec_data_addr; unsigned short mec_port_addr; u8 reg; mec_data_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, channel) & MEC_DATA_OFFSET_MASK; mec_port_addr = FIELD_GET(MEC_PORT_DWORD_OFFSET, channel) & MEC_PORT_OFFSET_MASK; mutex_lock(&mec_io_mutex); outb(mec_port_addr, MEC_ADDR); reg = inb(MEC_DATA_OFFSET(mec_data_addr)); /* Outputs are active low, so clear the bit for on, or set it for off */ if (on) reg &= ~(1 << (channel & MEC_PORT_CHANNEL_MASK)); else reg |= 1 << (channel & MEC_PORT_CHANNEL_MASK); outb(reg, MEC_DATA_OFFSET(mec_data_addr)); mutex_unlock(&mec_io_mutex); } static enum led_brightness silicom_mec_led_mc_brightness_get(struct led_classdev *led_cdev) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev); enum led_brightness brightness = LED_OFF; int i; for (i = 0; i < mc_cdev->num_colors; i++) { mc_cdev->subled_info[i].brightness = silicom_mec_led_get(mc_cdev->subled_info[i].channel); /* Mark the overall brightness as LED_ON if any of the subleds are on */ if (mc_cdev->subled_info[i].brightness != LED_OFF) brightness = LED_ON; } return brightness; } static void silicom_mec_led_mc_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev); int i; led_mc_calc_color_components(mc_cdev, brightness); for (i = 0; i < mc_cdev->num_colors; i++) { silicom_mec_port_set(mc_cdev->subled_info[i].channel, mc_cdev->subled_info[i].brightness); } } static int silicom_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) { u8 *channels = gpiochip_get_data(gc); /* Input registers have offsets between [0x00, 0x07] */ if (CHANNEL_TO_OFFSET(channels[offset]) < MEC_GPIO_IN_POS) return GPIO_LINE_DIRECTION_IN; return GPIO_LINE_DIRECTION_OUT; } static int silicom_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) { int direction = silicom_gpio_get_direction(gc, offset); return direction == GPIO_LINE_DIRECTION_IN ? 0 : -EINVAL; } static void silicom_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { int direction = silicom_gpio_get_direction(gc, offset); u8 *channels = gpiochip_get_data(gc); int channel = channels[offset]; if (direction == GPIO_LINE_DIRECTION_IN) return; silicom_mec_port_set(channel, !value); } static int silicom_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) { int direction = silicom_gpio_get_direction(gc, offset); if (direction == GPIO_LINE_DIRECTION_IN) return -EINVAL; silicom_gpio_set(gc, offset, value); return 0; } static int silicom_gpio_get(struct gpio_chip *gc, unsigned int offset) { u8 *channels = gpiochip_get_data(gc); int channel = channels[offset]; return silicom_mec_port_get(channel); } static struct mc_subled plat_0222_wan_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_WHITE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 7), }, { .color_index = LED_COLOR_ID_YELLOW, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 6), }, { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 5), }, }; static struct mc_subled plat_0222_sys_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_WHITE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 4), }, { .color_index = LED_COLOR_ID_AMBER, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 3), }, { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 2), }, }; static struct mc_subled plat_0222_stat1_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 1), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0c, 0), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 7), }, { .color_index = LED_COLOR_ID_YELLOW, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 6), }, }; static struct mc_subled plat_0222_stat2_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 5), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 4), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 3), }, { .color_index = LED_COLOR_ID_YELLOW, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 2), }, }; static struct mc_subled plat_0222_stat3_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 1), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0d, 0), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 1), }, { .color_index = LED_COLOR_ID_YELLOW, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x0e, 0), }, }; static struct led_classdev_mc plat_0222_mc_led_info[] __initdata = { { .led_cdev = { .name = "platled::wan", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(plat_0222_wan_mc_subled_info), .subled_info = plat_0222_wan_mc_subled_info, }, { .led_cdev = { .name = "platled::sys", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(plat_0222_sys_mc_subled_info), .subled_info = plat_0222_sys_mc_subled_info, }, { .led_cdev = { .name = "platled::stat1", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(plat_0222_stat1_mc_subled_info), .subled_info = plat_0222_stat1_mc_subled_info, }, { .led_cdev = { .name = "platled::stat2", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(plat_0222_stat2_mc_subled_info), .subled_info = plat_0222_stat2_mc_subled_info, }, { .led_cdev = { .name = "platled::stat3", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(plat_0222_stat3_mc_subled_info), .subled_info = plat_0222_stat3_mc_subled_info, }, { }, }; static struct gpio_chip silicom_gpio_chip = { .label = "silicom-gpio", .get_direction = silicom_gpio_get_direction, .direction_input = silicom_gpio_direction_input, .direction_output = silicom_gpio_direction_output, .get = silicom_gpio_get, .set = silicom_gpio_set, .base = -1, .ngpio = ARRAY_SIZE(plat_0222_gpio_channels), .names = plat_0222_gpio_names, /* * We're using a mutex to protect the indirect access, so we can sleep * if the lock blocks */ .can_sleep = true, }; static struct silicom_platform_info silicom_plat_0222_cordoba_info __initdata = { .io_base = MEC_IO_BASE, .io_len = MEC_IO_LEN, .led_info = plat_0222_mc_led_info, .gpiochip = &silicom_gpio_chip, .gpio_channels = plat_0222_gpio_channels, /* * The original generic cordoba does not have the last 4 outputs of the * plat_0222 variant, the rest are the same, so use the same longer list, * but ignore the last entries here */ .ngpio = ARRAY_SIZE(plat_0222_gpio_channels), }; static struct mc_subled cordoba_fp_left_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 6), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 5), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x09, 7), }, { .color_index = LED_COLOR_ID_AMBER, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x09, 4), }, }; static struct mc_subled cordoba_fp_center_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 7), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 4), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 3), }, { .color_index = LED_COLOR_ID_AMBER, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x09, 6), }, }; static struct mc_subled cordoba_fp_right_mc_subled_info[] __initdata = { { .color_index = LED_COLOR_ID_RED, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 2), }, { .color_index = LED_COLOR_ID_GREEN, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 1), }, { .color_index = LED_COLOR_ID_BLUE, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x08, 0), }, { .color_index = LED_COLOR_ID_AMBER, .brightness = 1, .intensity = 0, .channel = OFFSET_BIT_TO_CHANNEL(0x09, 5), }, }; static struct led_classdev_mc cordoba_mc_led_info[] __initdata = { { .led_cdev = { .name = "platled::fp_left", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(cordoba_fp_left_mc_subled_info), .subled_info = cordoba_fp_left_mc_subled_info, }, { .led_cdev = { .name = "platled::fp_center", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(cordoba_fp_center_mc_subled_info), .subled_info = cordoba_fp_center_mc_subled_info, }, { .led_cdev = { .name = "platled::fp_right", .brightness = 0, .max_brightness = 1, .brightness_set = silicom_mec_led_mc_brightness_set, .brightness_get = silicom_mec_led_mc_brightness_get, }, .num_colors = ARRAY_SIZE(cordoba_fp_right_mc_subled_info), .subled_info = cordoba_fp_right_mc_subled_info, }, { }, }; static struct silicom_platform_info silicom_generic_cordoba_info __initdata = { .io_base = MEC_IO_BASE, .io_len = MEC_IO_LEN, .led_info = cordoba_mc_led_info, .gpiochip = &silicom_gpio_chip, .gpio_channels = plat_0222_gpio_channels, .ngpio = ARRAY_SIZE(plat_0222_gpio_channels), }; /* * sysfs interface */ static ssize_t efuse_status_show(struct device *dev, struct device_attribute *attr, char *buf) { u32 reg; mutex_lock(&mec_io_mutex); /* Select memory region */ outb(IO_REG_BANK, EC_ADDR_MSB); outb(MEC_EFUSE_LSB_ADDR, EC_ADDR_LSB); /* Get current data from the address */ reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); mutex_unlock(&mec_io_mutex); efuse_status = reg & 0x1; return sysfs_emit(buf, "%u\n", efuse_status); } static DEVICE_ATTR_RO(efuse_status); static ssize_t uc_version_show(struct device *dev, struct device_attribute *attr, char *buf) { int uc_version; u32 reg; mutex_lock(&mec_io_mutex); outb(IO_REG_BANK, EC_ADDR_MSB); outb(DEFAULT_CHAN_LO, EC_ADDR_LSB); reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); mutex_unlock(&mec_io_mutex); uc_version = FIELD_GET(MEC_VERSION_LOC, reg); if (uc_version >= 192) return -EINVAL; uc_version = FIELD_GET(MEC_VERSION_MAJOR, reg) * 100 + FIELD_GET(MEC_VERSION_MINOR, reg); mec_uc_version = uc_version; return sysfs_emit(buf, "%u\n", mec_uc_version); } static DEVICE_ATTR_RO(uc_version); static ssize_t power_cycle_show(struct device *dev, struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%u\n", power_cycle); } static void powercycle_uc(void) { /* Select memory region */ outb(IO_REG_BANK, EC_ADDR_MSB); outb(MEC_POWER_CYCLE_ADDR, EC_ADDR_LSB); /* Set to 1 for current data from the address */ outb(1, MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); } static ssize_t power_cycle_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int rc; unsigned int power_cycle_cmd; rc = kstrtou32(buf, 0, &power_cycle_cmd); if (rc) return -EINVAL; if (power_cycle_cmd > 0) { mutex_lock(&mec_io_mutex); power_cycle = power_cycle_cmd; powercycle_uc(); mutex_unlock(&mec_io_mutex); } return count; } static DEVICE_ATTR_RW(power_cycle); static struct attribute *silicom_attrs[] = { &dev_attr_efuse_status.attr, &dev_attr_uc_version.attr, &dev_attr_power_cycle.attr, NULL, }; ATTRIBUTE_GROUPS(silicom); static struct platform_driver silicom_platform_driver = { .driver = { .name = "silicom-platform", .dev_groups = silicom_groups, }, }; static int __init silicom_mc_leds_register(struct device *dev, const struct led_classdev_mc *mc_leds) { int size = sizeof(struct mc_subled); struct led_classdev_mc *led; int i, err; for (i = 0; mc_leds[i].led_cdev.name; i++) { led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; memcpy(led, &mc_leds[i], sizeof(*led)); led->subled_info = devm_kzalloc(dev, led->num_colors * size, GFP_KERNEL); if (!led->subled_info) return -ENOMEM; memcpy(led->subled_info, mc_leds[i].subled_info, led->num_colors * size); err = devm_led_classdev_multicolor_register(dev, led); if (err) return err; } return 0; } static u32 rpm_get(void) { u32 reg; mutex_lock(&mec_io_mutex); /* Select memory region */ outb(IO_REG_BANK, EC_ADDR_MSB); outb(DEFAULT_CHAN_LO_T, EC_ADDR_LSB); reg = inw(MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); mutex_unlock(&mec_io_mutex); return reg; } static u32 temp_get(void) { u32 reg; mutex_lock(&mec_io_mutex); /* Select memory region */ outb(IO_REG_BANK, EC_ADDR_MSB); outb(DEFAULT_CHAN_LO_T, EC_ADDR_LSB); reg = inl(MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); mutex_unlock(&mec_io_mutex); return FIELD_GET(MEC_TEMP_LOC, reg) * 100; } static umode_t silicom_fan_control_fan_is_visible(const u32 attr) { switch (attr) { case hwmon_fan_input: case hwmon_fan_label: return 0444; default: return 0; } } static umode_t silicom_fan_control_temp_is_visible(const u32 attr) { switch (attr) { case hwmon_temp_input: case hwmon_temp_label: return 0444; default: return 0; } } static int silicom_fan_control_read_fan(struct device *dev, u32 attr, long *val) { switch (attr) { case hwmon_fan_input: *val = rpm_get(); return 0; default: return -EOPNOTSUPP; } } static int silicom_fan_control_read_temp(struct device *dev, u32 attr, long *val) { switch (attr) { case hwmon_temp_input: *val = temp_get(); return 0; default: return -EOPNOTSUPP; } } static umode_t silicom_fan_control_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_fan: return silicom_fan_control_fan_is_visible(attr); case hwmon_temp: return silicom_fan_control_temp_is_visible(attr); default: return 0; } } static int silicom_fan_control_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { case hwmon_fan: return silicom_fan_control_read_fan(dev, attr, val); case hwmon_temp: return silicom_fan_control_read_temp(dev, attr, val); default: return -EOPNOTSUPP; } } static int silicom_fan_control_read_labels(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, const char **str) { switch (type) { case hwmon_fan: *str = "Silicom_platform: Fan Speed"; return 0; case hwmon_temp: *str = "Silicom_platform: Thermostat Sensor"; return 0; default: return -EOPNOTSUPP; } } static const struct hwmon_ops silicom_fan_control_hwmon_ops = { .is_visible = silicom_fan_control_is_visible, .read = silicom_fan_control_read, .read_string = silicom_fan_control_read_labels, }; static const struct hwmon_chip_info silicom_chip_info = { .ops = &silicom_fan_control_hwmon_ops, .info = silicom_fan_control_info, }; static int __init silicom_platform_probe(struct platform_device *device) { struct device *hwmon_dev; u8 magic, ver; int err; if (!devm_request_region(&device->dev, MEC_IO_BASE, MEC_IO_LEN, "mec")) { dev_err(&device->dev, "couldn't reserve MEC io ports\n"); return -EBUSY; } /* Sanity check magic number read for EC */ outb(IO_REG_BANK, MEC_ADDR); magic = inb(MEC_DATA_OFFSET(DEFAULT_CHAN_LO)); ver = inb(MEC_DATA_OFFSET(DEFAULT_CHAN_HI)); dev_dbg(&device->dev, "EC magic 0x%02x, version 0x%02x\n", magic, ver); if (magic != SILICOM_MEC_MAGIC) { dev_err(&device->dev, "Bad EC magic 0x%02x!\n", magic); return -ENODEV; } err = silicom_mc_leds_register(&device->dev, silicom_led_info); if (err) { dev_err(&device->dev, "Failed to register LEDs\n"); return err; } err = devm_gpiochip_add_data(&device->dev, silicom_gpiochip, silicom_gpio_channels); if (err) { dev_err(&device->dev, "Failed to register gpiochip: %d\n", err); return err; } hwmon_dev = devm_hwmon_device_register_with_info(&device->dev, "silicom_fan", NULL, &silicom_chip_info, NULL); err = PTR_ERR_OR_ZERO(hwmon_dev); if (err) { dev_err(&device->dev, "Failed to register hwmon_dev: %d\n", err); return err; } return err; } static int __init silicom_platform_info_init(const struct dmi_system_id *id) { struct silicom_platform_info *info = id->driver_data; silicom_led_info = info->led_info; silicom_gpio_channels = info->gpio_channels; silicom_gpiochip = info->gpiochip; silicom_gpiochip->ngpio = info->ngpio; return 1; } static const struct dmi_system_id silicom_dmi_ids[] __initconst = { { .callback = silicom_platform_info_init, .ident = "Silicom Cordoba (Generic)", .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"), DMI_MATCH(DMI_BOARD_NAME, "80300-0214-G"), }, .driver_data = &silicom_generic_cordoba_info, }, { .callback = silicom_platform_info_init, .ident = "Silicom Cordoba (Generic)", .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"), DMI_MATCH(DMI_BOARD_NAME, "80500-0214-G"), }, .driver_data = &silicom_generic_cordoba_info, }, { .callback = silicom_platform_info_init, .ident = "Silicom Cordoba (plat_0222)", .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Silicom"), DMI_MATCH(DMI_BOARD_NAME, "80300-0222-G"), }, .driver_data = &silicom_plat_0222_cordoba_info, }, { }, }; MODULE_DEVICE_TABLE(dmi, silicom_dmi_ids); static int __init silicom_platform_init(void) { if (!dmi_check_system(silicom_dmi_ids)) { pr_err("No DMI match for this platform\n"); return -ENODEV; } silicom_platform_dev = platform_create_bundle(&silicom_platform_driver, silicom_platform_probe, NULL, 0, NULL, 0); return PTR_ERR_OR_ZERO(silicom_platform_dev); } static void __exit silicom_platform_exit(void) { platform_device_unregister(silicom_platform_dev); platform_driver_unregister(&silicom_platform_driver); } module_init(silicom_platform_init); module_exit(silicom_platform_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Henry Shi "); MODULE_DESCRIPTION("Platform driver for Silicom network appliances");