1// SPDX-License-Identifier: GPL-2.0+
2//
3// silicom-platform.c - Silicom MEC170x platform driver
4//
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 0x24
26#define MEC_EFUSE_LSB_ADDR 0x28
27#define MEC_GPIO_IN_POS 0x08
28#define MEC_IO_BASE 0x0800
29#define MEC_IO_LEN 0x8
30#define IO_REG_BANK 0x0
31#define DEFAULT_CHAN_LO 0
32#define DEFAULT_CHAN_HI 0
33#define DEFAULT_CHAN_LO_T 0xc
34#define MEC_ADDR (MEC_IO_BASE + 0x02)
35#define EC_ADDR_LSB MEC_ADDR
36#define SILICOM_MEC_MAGIC 0x5a
37
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 NULL
63};
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(value: mec_port_addr, MEC_ADDR);
164 reg = inb(MEC_DATA_OFFSET(mec_data_addr));
165 mutex_unlock(lock: &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(offset: 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(value: 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 else
193 reg |= 1 << (channel & MEC_PORT_CHANNEL_MASK);
194 outb(value: reg, MEC_DATA_OFFSET(mec_data_addr));
195 mutex_unlock(lock: &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(channel: 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(mcled_cdev: mc_cdev, brightness);
222 for (i = 0; i < mc_cdev->num_colors; i++) {
223 silicom_mec_port_set(channel: mc_cdev->subled_info[i].channel,
224 on: 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, on: !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(offset: 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 sleep
478 * if the lock blocks
479 */
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 the
491 * plat_0222 variant, the rest are the same, so use the same longer list,
492 * but ignore the last entries here
493 */
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 interface
627 */
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(lock: &mec_io_mutex);
642
643 efuse_status = reg & 0x1;
644
645 return sysfs_emit(buf, fmt: "%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(lock: &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, fmt: "%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, fmt: "%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(value: 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(s: buf, base: 0, res: &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(lock: &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, size: 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, size: 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(parent: dev, mcled_cdev: 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(lock: &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(lock: &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(dev: &device->dev, mc_leds: 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(dev: &device->dev, name: "silicom_fan", NULL,
921 info: &silicom_chip_info, NULL);
922 err = PTR_ERR_OR_ZERO(ptr: 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(list: 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(ptr: 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

source code of linux/drivers/platform/x86/silicom-platform.c