0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035 #include <linux/acpi.h>
0036 #include <linux/device.h>
0037 #include <linux/dmi.h>
0038 #include <linux/hwmon.h>
0039 #include <linux/hwmon-sysfs.h>
0040 #include <linux/io.h>
0041 #include <linux/leds.h>
0042 #include <linux/module.h>
0043 #include <linux/mod_devicetable.h>
0044 #include <linux/platform_device.h>
0045 #include <linux/slab.h>
0046
0047 #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500
0048
0049
0050 #define MLXCPLD_LED_OFFSET_HALF 0x01
0051 #define MLXCPLD_LED_OFFSET_FULL 0x02
0052 #define MLXCPLD_LED_IS_OFF 0x00
0053 #define MLXCPLD_LED_RED_STATIC_ON 0x05
0054 #define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \
0055 MLXCPLD_LED_OFFSET_HALF)
0056 #define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \
0057 MLXCPLD_LED_OFFSET_FULL)
0058 #define MLXCPLD_LED_GREEN_STATIC_ON 0x0D
0059 #define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \
0060 MLXCPLD_LED_OFFSET_HALF)
0061 #define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \
0062 MLXCPLD_LED_OFFSET_FULL)
0063 #define MLXCPLD_LED_BLINK_3HZ 167
0064 #define MLXCPLD_LED_BLINK_6HZ 83
0065
0066
0067
0068
0069
0070
0071
0072 struct mlxcpld_param {
0073 u8 offset;
0074 u8 mask;
0075 u8 base_color;
0076 };
0077
0078
0079
0080
0081
0082
0083 struct mlxcpld_led_priv {
0084 struct led_classdev cdev;
0085 struct mlxcpld_param param;
0086 };
0087
0088 #define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev)
0089
0090
0091
0092
0093
0094
0095
0096
0097
0098 struct mlxcpld_led_profile {
0099 u8 offset;
0100 u8 mask;
0101 u8 base_color;
0102 enum led_brightness brightness;
0103 const char *name;
0104 };
0105
0106
0107
0108
0109
0110
0111
0112
0113
0114 struct mlxcpld_led_pdata {
0115 struct platform_device *pdev;
0116 struct mlxcpld_led_priv *pled;
0117 struct mlxcpld_led_profile *profile;
0118 int num_led_instances;
0119 spinlock_t lock;
0120 };
0121
0122 static struct mlxcpld_led_pdata *mlxcpld_led;
0123
0124
0125
0126
0127
0128 static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = {
0129 {
0130 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0131 "mlxcpld:fan1:green",
0132 },
0133 {
0134 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0135 "mlxcpld:fan1:red",
0136 },
0137 {
0138 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0139 "mlxcpld:fan2:green",
0140 },
0141 {
0142 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0143 "mlxcpld:fan2:red",
0144 },
0145 {
0146 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0147 "mlxcpld:fan3:green",
0148 },
0149 {
0150 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0151 "mlxcpld:fan3:red",
0152 },
0153 {
0154 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0155 "mlxcpld:fan4:green",
0156 },
0157 {
0158 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0159 "mlxcpld:fan4:red",
0160 },
0161 {
0162 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0163 "mlxcpld:psu:green",
0164 },
0165 {
0166 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0167 "mlxcpld:psu:red",
0168 },
0169 {
0170 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0171 "mlxcpld:status:green",
0172 },
0173 {
0174 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0175 "mlxcpld:status:red",
0176 },
0177 };
0178
0179
0180 static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = {
0181 {
0182 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0183 "mlxcpld:fan:green",
0184 },
0185 {
0186 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0187 "mlxcpld:fan:red",
0188 },
0189 {
0190 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0191 "mlxcpld:psu1:green",
0192 },
0193 {
0194 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0195 "mlxcpld:psu1:red",
0196 },
0197 {
0198 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0199 "mlxcpld:psu2:green",
0200 },
0201 {
0202 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0203 "mlxcpld:psu2:red",
0204 },
0205 {
0206 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
0207 "mlxcpld:status:green",
0208 },
0209 {
0210 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
0211 "mlxcpld:status:red",
0212 },
0213 {
0214 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF,
0215 "mlxcpld:uid:blue",
0216 },
0217 };
0218
0219 enum mlxcpld_led_platform_types {
0220 MLXCPLD_LED_PLATFORM_DEFAULT,
0221 MLXCPLD_LED_PLATFORM_MSN2100,
0222 };
0223
0224 static const char *mlx_product_names[] = {
0225 "DEFAULT",
0226 "MSN2100",
0227 };
0228
0229 static enum
0230 mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void)
0231 {
0232 const char *mlx_product_name;
0233 int i;
0234
0235 mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
0236 if (!mlx_product_name)
0237 return MLXCPLD_LED_PLATFORM_DEFAULT;
0238
0239 for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) {
0240 if (strstr(mlx_product_name, mlx_product_names[i]))
0241 return i;
0242 }
0243
0244 return MLXCPLD_LED_PLATFORM_DEFAULT;
0245 }
0246
0247 static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag,
0248 u8 *data)
0249 {
0250 u32 addr = base + offset;
0251
0252 if (rw_flag == 0)
0253 outb(*data, addr);
0254 else
0255 *data = inb(addr);
0256 }
0257
0258 static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset)
0259 {
0260 u8 nib, val;
0261
0262
0263
0264
0265
0266
0267
0268
0269
0270
0271
0272 spin_lock(&mlxcpld_led->lock);
0273 mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1,
0274 &val);
0275 nib = (mask == 0xf0) ? vset : (vset << 4);
0276 val = (val & mask) | nib;
0277 mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0,
0278 &val);
0279 spin_unlock(&mlxcpld_led->lock);
0280 }
0281
0282 static void mlxcpld_led_brightness_set(struct led_classdev *led,
0283 enum led_brightness value)
0284 {
0285 struct mlxcpld_led_priv *pled = cdev_to_priv(led);
0286
0287 if (value) {
0288 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
0289 pled->param.base_color);
0290 return;
0291 }
0292
0293 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
0294 MLXCPLD_LED_IS_OFF);
0295 }
0296
0297 static int mlxcpld_led_blink_set(struct led_classdev *led,
0298 unsigned long *delay_on,
0299 unsigned long *delay_off)
0300 {
0301 struct mlxcpld_led_priv *pled = cdev_to_priv(led);
0302
0303
0304
0305
0306
0307 if (!(*delay_on == 0 && *delay_off == 0) &&
0308 !(*delay_on == MLXCPLD_LED_BLINK_3HZ &&
0309 *delay_off == MLXCPLD_LED_BLINK_3HZ) &&
0310 !(*delay_on == MLXCPLD_LED_BLINK_6HZ &&
0311 *delay_off == MLXCPLD_LED_BLINK_6HZ))
0312 return -EINVAL;
0313
0314 if (*delay_on == MLXCPLD_LED_BLINK_6HZ)
0315 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
0316 pled->param.base_color +
0317 MLXCPLD_LED_OFFSET_FULL);
0318 else
0319 mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
0320 pled->param.base_color +
0321 MLXCPLD_LED_OFFSET_HALF);
0322
0323 return 0;
0324 }
0325
0326 static int mlxcpld_led_config(struct device *dev,
0327 struct mlxcpld_led_pdata *cpld)
0328 {
0329 int i;
0330 int err;
0331
0332 cpld->pled = devm_kcalloc(dev,
0333 cpld->num_led_instances,
0334 sizeof(struct mlxcpld_led_priv),
0335 GFP_KERNEL);
0336 if (!cpld->pled)
0337 return -ENOMEM;
0338
0339 for (i = 0; i < cpld->num_led_instances; i++) {
0340 cpld->pled[i].cdev.name = cpld->profile[i].name;
0341 cpld->pled[i].cdev.brightness = cpld->profile[i].brightness;
0342 cpld->pled[i].cdev.max_brightness = 1;
0343 cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set;
0344 cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set;
0345 cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME;
0346 err = devm_led_classdev_register(dev, &cpld->pled[i].cdev);
0347 if (err)
0348 return err;
0349
0350 cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset;
0351 cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask;
0352 cpld->pled[i].param.base_color =
0353 mlxcpld_led->profile[i].base_color;
0354
0355 if (mlxcpld_led->profile[i].brightness)
0356 mlxcpld_led_brightness_set(&cpld->pled[i].cdev,
0357 mlxcpld_led->profile[i].brightness);
0358 }
0359
0360 return 0;
0361 }
0362
0363 static int __init mlxcpld_led_probe(struct platform_device *pdev)
0364 {
0365 enum mlxcpld_led_platform_types mlxcpld_led_plat =
0366 mlxcpld_led_platform_check_sys_type();
0367
0368 mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led),
0369 GFP_KERNEL);
0370 if (!mlxcpld_led)
0371 return -ENOMEM;
0372
0373 mlxcpld_led->pdev = pdev;
0374
0375 switch (mlxcpld_led_plat) {
0376 case MLXCPLD_LED_PLATFORM_MSN2100:
0377 mlxcpld_led->profile = mlxcpld_led_msn2100_profile;
0378 mlxcpld_led->num_led_instances =
0379 ARRAY_SIZE(mlxcpld_led_msn2100_profile);
0380 break;
0381
0382 default:
0383 mlxcpld_led->profile = mlxcpld_led_default_profile;
0384 mlxcpld_led->num_led_instances =
0385 ARRAY_SIZE(mlxcpld_led_default_profile);
0386 break;
0387 }
0388
0389 spin_lock_init(&mlxcpld_led->lock);
0390
0391 return mlxcpld_led_config(&pdev->dev, mlxcpld_led);
0392 }
0393
0394 static struct platform_driver mlxcpld_led_driver = {
0395 .driver = {
0396 .name = KBUILD_MODNAME,
0397 },
0398 };
0399
0400 static int __init mlxcpld_led_init(void)
0401 {
0402 struct platform_device *pdev;
0403 int err;
0404
0405 if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd."))
0406 return -ENODEV;
0407
0408 pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
0409 if (IS_ERR(pdev)) {
0410 pr_err("Device allocation failed\n");
0411 return PTR_ERR(pdev);
0412 }
0413
0414 err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe);
0415 if (err) {
0416 pr_err("Probe platform driver failed\n");
0417 platform_device_unregister(pdev);
0418 }
0419
0420 return err;
0421 }
0422
0423 static void __exit mlxcpld_led_exit(void)
0424 {
0425 platform_device_unregister(mlxcpld_led->pdev);
0426 platform_driver_unregister(&mlxcpld_led_driver);
0427 }
0428
0429 module_init(mlxcpld_led_init);
0430 module_exit(mlxcpld_led_exit);
0431
0432 MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
0433 MODULE_DESCRIPTION("Mellanox board LED driver");
0434 MODULE_LICENSE("Dual BSD/GPL");
0435 MODULE_ALIAS("platform:leds_mlxcpld");