mlxcpld-hotplug.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. /*
  2. * drivers/platform/x86/mlxcpld-hotplug.c
  3. * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
  4. * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the names of the copyright holders nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * Alternatively, this software may be distributed under the terms of the
  19. * GNU General Public License ("GPL") version 2 as published by the Free
  20. * Software Foundation.
  21. *
  22. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  26. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  27. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  28. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  29. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  30. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32. * POSSIBILITY OF SUCH DAMAGE.
  33. */
  34. #include <linux/bitops.h>
  35. #include <linux/device.h>
  36. #include <linux/hwmon.h>
  37. #include <linux/hwmon-sysfs.h>
  38. #include <linux/i2c.h>
  39. #include <linux/interrupt.h>
  40. #include <linux/io.h>
  41. #include <linux/module.h>
  42. #include <linux/platform_data/mlxcpld-hotplug.h>
  43. #include <linux/platform_device.h>
  44. #include <linux/spinlock.h>
  45. #include <linux/wait.h>
  46. #include <linux/workqueue.h>
  47. /* Offset of event and mask registers from status register */
  48. #define MLXCPLD_HOTPLUG_EVENT_OFF 1
  49. #define MLXCPLD_HOTPLUG_MASK_OFF 2
  50. #define MLXCPLD_HOTPLUG_AGGR_MASK_OFF 1
  51. #define MLXCPLD_HOTPLUG_ATTRS_NUM 8
  52. /**
  53. * enum mlxcpld_hotplug_attr_type - sysfs attributes for hotplug events:
  54. * @MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute;
  55. * @MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: power cable attribute;
  56. * @MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute;
  57. */
  58. enum mlxcpld_hotplug_attr_type {
  59. MLXCPLD_HOTPLUG_ATTR_TYPE_PSU,
  60. MLXCPLD_HOTPLUG_ATTR_TYPE_PWR,
  61. MLXCPLD_HOTPLUG_ATTR_TYPE_FAN,
  62. };
  63. /**
  64. * struct mlxcpld_hotplug_priv_data - platform private data:
  65. * @irq: platform interrupt number;
  66. * @pdev: platform device;
  67. * @plat: platform data;
  68. * @hwmon: hwmon device;
  69. * @mlxcpld_hotplug_attr: sysfs attributes array;
  70. * @mlxcpld_hotplug_dev_attr: sysfs sensor device attribute array;
  71. * @group: sysfs attribute group;
  72. * @groups: list of sysfs attribute group for hwmon registration;
  73. * @dwork: delayed work template;
  74. * @lock: spin lock;
  75. * @aggr_cache: last value of aggregation register status;
  76. * @psu_cache: last value of PSU register status;
  77. * @pwr_cache: last value of power register status;
  78. * @fan_cache: last value of FAN register status;
  79. */
  80. struct mlxcpld_hotplug_priv_data {
  81. int irq;
  82. struct platform_device *pdev;
  83. struct mlxcpld_hotplug_platform_data *plat;
  84. struct device *hwmon;
  85. struct attribute *mlxcpld_hotplug_attr[MLXCPLD_HOTPLUG_ATTRS_NUM + 1];
  86. struct sensor_device_attribute_2
  87. mlxcpld_hotplug_dev_attr[MLXCPLD_HOTPLUG_ATTRS_NUM];
  88. struct attribute_group group;
  89. const struct attribute_group *groups[2];
  90. struct delayed_work dwork;
  91. spinlock_t lock;
  92. u8 aggr_cache;
  93. u8 psu_cache;
  94. u8 pwr_cache;
  95. u8 fan_cache;
  96. };
  97. static ssize_t mlxcpld_hotplug_attr_show(struct device *dev,
  98. struct device_attribute *attr,
  99. char *buf)
  100. {
  101. struct platform_device *pdev = to_platform_device(dev);
  102. struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev);
  103. int index = to_sensor_dev_attr_2(attr)->index;
  104. int nr = to_sensor_dev_attr_2(attr)->nr;
  105. u8 reg_val = 0;
  106. switch (nr) {
  107. case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU:
  108. /* Bit = 0 : PSU is present. */
  109. reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index));
  110. break;
  111. case MLXCPLD_HOTPLUG_ATTR_TYPE_PWR:
  112. /* Bit = 1 : power cable is attached. */
  113. reg_val = !!(inb(priv->plat->pwr_reg_offset) & BIT(index %
  114. priv->plat->pwr_count));
  115. break;
  116. case MLXCPLD_HOTPLUG_ATTR_TYPE_FAN:
  117. /* Bit = 0 : FAN is present. */
  118. reg_val = !!!(inb(priv->plat->fan_reg_offset) & BIT(index %
  119. priv->plat->fan_count));
  120. break;
  121. }
  122. return sprintf(buf, "%u\n", reg_val);
  123. }
  124. #define PRIV_ATTR(i) priv->mlxcpld_hotplug_attr[i]
  125. #define PRIV_DEV_ATTR(i) priv->mlxcpld_hotplug_dev_attr[i]
  126. static int mlxcpld_hotplug_attr_init(struct mlxcpld_hotplug_priv_data *priv)
  127. {
  128. int num_attrs = priv->plat->psu_count + priv->plat->pwr_count +
  129. priv->plat->fan_count;
  130. int i;
  131. priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs *
  132. sizeof(struct attribute *),
  133. GFP_KERNEL);
  134. if (!priv->group.attrs)
  135. return -ENOMEM;
  136. for (i = 0; i < num_attrs; i++) {
  137. PRIV_ATTR(i) = &PRIV_DEV_ATTR(i).dev_attr.attr;
  138. if (i < priv->plat->psu_count) {
  139. PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
  140. GFP_KERNEL, "psu%u", i + 1);
  141. PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PSU;
  142. } else if (i < priv->plat->psu_count + priv->plat->pwr_count) {
  143. PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
  144. GFP_KERNEL, "pwr%u", i %
  145. priv->plat->pwr_count + 1);
  146. PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PWR;
  147. } else {
  148. PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev,
  149. GFP_KERNEL, "fan%u", i %
  150. priv->plat->fan_count + 1);
  151. PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_FAN;
  152. }
  153. if (!PRIV_ATTR(i)->name) {
  154. dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n",
  155. i + 1);
  156. return -ENOMEM;
  157. }
  158. PRIV_DEV_ATTR(i).dev_attr.attr.name = PRIV_ATTR(i)->name;
  159. PRIV_DEV_ATTR(i).dev_attr.attr.mode = S_IRUGO;
  160. PRIV_DEV_ATTR(i).dev_attr.show = mlxcpld_hotplug_attr_show;
  161. PRIV_DEV_ATTR(i).index = i;
  162. sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr);
  163. }
  164. priv->group.attrs = priv->mlxcpld_hotplug_attr;
  165. priv->groups[0] = &priv->group;
  166. priv->groups[1] = NULL;
  167. return 0;
  168. }
  169. static int mlxcpld_hotplug_device_create(struct device *dev,
  170. struct mlxcpld_hotplug_device *item)
  171. {
  172. item->adapter = i2c_get_adapter(item->bus);
  173. if (!item->adapter) {
  174. dev_err(dev, "Failed to get adapter for bus %d\n",
  175. item->bus);
  176. return -EFAULT;
  177. }
  178. item->client = i2c_new_device(item->adapter, &item->brdinfo);
  179. if (!item->client) {
  180. dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n",
  181. item->brdinfo.type, item->bus, item->brdinfo.addr);
  182. i2c_put_adapter(item->adapter);
  183. item->adapter = NULL;
  184. return -EFAULT;
  185. }
  186. return 0;
  187. }
  188. static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device *item)
  189. {
  190. if (item->client) {
  191. i2c_unregister_device(item->client);
  192. item->client = NULL;
  193. }
  194. if (item->adapter) {
  195. i2c_put_adapter(item->adapter);
  196. item->adapter = NULL;
  197. }
  198. }
  199. static inline void
  200. mlxcpld_hotplug_work_helper(struct device *dev,
  201. struct mlxcpld_hotplug_device *item, u8 is_inverse,
  202. u16 offset, u8 mask, u8 *cache)
  203. {
  204. u8 val, asserted;
  205. int bit;
  206. /* Mask event. */
  207. outb(0, offset + MLXCPLD_HOTPLUG_MASK_OFF);
  208. /* Read status. */
  209. val = inb(offset) & mask;
  210. asserted = *cache ^ val;
  211. *cache = val;
  212. /*
  213. * Validate if item related to received signal type is valid.
  214. * It should never happen, excepted the situation when some
  215. * piece of hardware is broken. In such situation just produce
  216. * error message and return. Caller must continue to handle the
  217. * signals from other devices if any.
  218. */
  219. if (unlikely(!item)) {
  220. dev_err(dev, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n",
  221. offset, mask);
  222. return;
  223. }
  224. for_each_set_bit(bit, (unsigned long *)&asserted, 8) {
  225. if (val & BIT(bit)) {
  226. if (is_inverse)
  227. mlxcpld_hotplug_device_destroy(item + bit);
  228. else
  229. mlxcpld_hotplug_device_create(dev, item + bit);
  230. } else {
  231. if (is_inverse)
  232. mlxcpld_hotplug_device_create(dev, item + bit);
  233. else
  234. mlxcpld_hotplug_device_destroy(item + bit);
  235. }
  236. }
  237. /* Acknowledge event. */
  238. outb(0, offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  239. /* Unmask event. */
  240. outb(mask, offset + MLXCPLD_HOTPLUG_MASK_OFF);
  241. }
  242. /*
  243. * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt
  244. * registers according to the below hierarchy schema:
  245. *
  246. * Aggregation registers (status/mask)
  247. * PSU registers: *---*
  248. * *-----------------* | |
  249. * |status/event/mask|----->| * |
  250. * *-----------------* | |
  251. * Power registers: | |
  252. * *-----------------* | |
  253. * |status/event/mask|----->| * |---> CPU
  254. * *-----------------* | |
  255. * FAN registers:
  256. * *-----------------* | |
  257. * |status/event/mask|----->| * |
  258. * *-----------------* | |
  259. * *---*
  260. * In case some system changed are detected: FAN in/out, PSU in/out, power
  261. * cable attached/detached, relevant device is created or destroyed.
  262. */
  263. static void mlxcpld_hotplug_work_handler(struct work_struct *work)
  264. {
  265. struct mlxcpld_hotplug_priv_data *priv = container_of(work,
  266. struct mlxcpld_hotplug_priv_data, dwork.work);
  267. u8 val, aggr_asserted;
  268. unsigned long flags;
  269. /* Mask aggregation event. */
  270. outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
  271. /* Read aggregation status. */
  272. val = inb(priv->plat->top_aggr_offset) & priv->plat->top_aggr_mask;
  273. aggr_asserted = priv->aggr_cache ^ val;
  274. priv->aggr_cache = val;
  275. /* Handle PSU configuration changes. */
  276. if (aggr_asserted & priv->plat->top_aggr_psu_mask)
  277. mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->psu,
  278. 1, priv->plat->psu_reg_offset,
  279. priv->plat->psu_mask,
  280. &priv->psu_cache);
  281. /* Handle power cable configuration changes. */
  282. if (aggr_asserted & priv->plat->top_aggr_pwr_mask)
  283. mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->pwr,
  284. 0, priv->plat->pwr_reg_offset,
  285. priv->plat->pwr_mask,
  286. &priv->pwr_cache);
  287. /* Handle FAN configuration changes. */
  288. if (aggr_asserted & priv->plat->top_aggr_fan_mask)
  289. mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->fan,
  290. 1, priv->plat->fan_reg_offset,
  291. priv->plat->fan_mask,
  292. &priv->fan_cache);
  293. if (aggr_asserted) {
  294. spin_lock_irqsave(&priv->lock, flags);
  295. /*
  296. * It is possible, that some signals have been inserted, while
  297. * interrupt has been masked by mlxcpld_hotplug_work_handler.
  298. * In this case such signals will be missed. In order to handle
  299. * these signals delayed work is canceled and work task
  300. * re-scheduled for immediate execution. It allows to handle
  301. * missed signals, if any. In other case work handler just
  302. * validates that no new signals have been received during
  303. * masking.
  304. */
  305. cancel_delayed_work(&priv->dwork);
  306. schedule_delayed_work(&priv->dwork, 0);
  307. spin_unlock_irqrestore(&priv->lock, flags);
  308. return;
  309. }
  310. /* Unmask aggregation event (no need acknowledge). */
  311. outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset +
  312. MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
  313. }
  314. static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data *priv)
  315. {
  316. /* Clear psu presense event. */
  317. outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  318. /* Set psu initial status as mask and unmask psu event. */
  319. priv->psu_cache = priv->plat->psu_mask;
  320. outb(priv->plat->psu_mask, priv->plat->psu_reg_offset +
  321. MLXCPLD_HOTPLUG_MASK_OFF);
  322. /* Clear power cable event. */
  323. outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  324. /* Keep power initial status as zero and unmask power event. */
  325. outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset +
  326. MLXCPLD_HOTPLUG_MASK_OFF);
  327. /* Clear fan presense event. */
  328. outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  329. /* Set fan initial status as mask and unmask fan event. */
  330. priv->fan_cache = priv->plat->fan_mask;
  331. outb(priv->plat->fan_mask, priv->plat->fan_reg_offset +
  332. MLXCPLD_HOTPLUG_MASK_OFF);
  333. /* Keep aggregation initial status as zero and unmask events. */
  334. outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset +
  335. MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
  336. /* Invoke work handler for initializing hot plug devices setting. */
  337. mlxcpld_hotplug_work_handler(&priv->dwork.work);
  338. enable_irq(priv->irq);
  339. }
  340. static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data *priv)
  341. {
  342. int i;
  343. disable_irq(priv->irq);
  344. cancel_delayed_work_sync(&priv->dwork);
  345. /* Mask aggregation event. */
  346. outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF);
  347. /* Mask psu presense event. */
  348. outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
  349. /* Clear psu presense event. */
  350. outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  351. /* Mask power cable event. */
  352. outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
  353. /* Clear power cable event. */
  354. outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  355. /* Mask fan presense event. */
  356. outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF);
  357. /* Clear fan presense event. */
  358. outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF);
  359. /* Remove all the attached devices. */
  360. for (i = 0; i < priv->plat->psu_count; i++)
  361. mlxcpld_hotplug_device_destroy(priv->plat->psu + i);
  362. for (i = 0; i < priv->plat->pwr_count; i++)
  363. mlxcpld_hotplug_device_destroy(priv->plat->pwr + i);
  364. for (i = 0; i < priv->plat->fan_count; i++)
  365. mlxcpld_hotplug_device_destroy(priv->plat->fan + i);
  366. }
  367. static irqreturn_t mlxcpld_hotplug_irq_handler(int irq, void *dev)
  368. {
  369. struct mlxcpld_hotplug_priv_data *priv =
  370. (struct mlxcpld_hotplug_priv_data *)dev;
  371. /* Schedule work task for immediate execution.*/
  372. schedule_delayed_work(&priv->dwork, 0);
  373. return IRQ_HANDLED;
  374. }
  375. static int mlxcpld_hotplug_probe(struct platform_device *pdev)
  376. {
  377. struct mlxcpld_hotplug_platform_data *pdata;
  378. struct mlxcpld_hotplug_priv_data *priv;
  379. int err;
  380. pdata = dev_get_platdata(&pdev->dev);
  381. if (!pdata) {
  382. dev_err(&pdev->dev, "Failed to get platform data.\n");
  383. return -EINVAL;
  384. }
  385. priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  386. if (!priv)
  387. return -ENOMEM;
  388. priv->pdev = pdev;
  389. priv->plat = pdata;
  390. priv->irq = platform_get_irq(pdev, 0);
  391. if (priv->irq < 0) {
  392. dev_err(&pdev->dev, "Failed to get platform irq: %d\n",
  393. priv->irq);
  394. return priv->irq;
  395. }
  396. err = devm_request_irq(&pdev->dev, priv->irq,
  397. mlxcpld_hotplug_irq_handler, 0, pdev->name,
  398. priv);
  399. if (err) {
  400. dev_err(&pdev->dev, "Failed to request irq: %d\n", err);
  401. return err;
  402. }
  403. disable_irq(priv->irq);
  404. INIT_DELAYED_WORK(&priv->dwork, mlxcpld_hotplug_work_handler);
  405. spin_lock_init(&priv->lock);
  406. err = mlxcpld_hotplug_attr_init(priv);
  407. if (err) {
  408. dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", err);
  409. return err;
  410. }
  411. priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev,
  412. "mlxcpld_hotplug", priv, priv->groups);
  413. if (IS_ERR(priv->hwmon)) {
  414. dev_err(&pdev->dev, "Failed to register hwmon device %ld\n",
  415. PTR_ERR(priv->hwmon));
  416. return PTR_ERR(priv->hwmon);
  417. }
  418. platform_set_drvdata(pdev, priv);
  419. /* Perform initial interrupts setup. */
  420. mlxcpld_hotplug_set_irq(priv);
  421. return 0;
  422. }
  423. static int mlxcpld_hotplug_remove(struct platform_device *pdev)
  424. {
  425. struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev);
  426. /* Clean interrupts setup. */
  427. mlxcpld_hotplug_unset_irq(priv);
  428. return 0;
  429. }
  430. static struct platform_driver mlxcpld_hotplug_driver = {
  431. .driver = {
  432. .name = "mlxcpld-hotplug",
  433. },
  434. .probe = mlxcpld_hotplug_probe,
  435. .remove = mlxcpld_hotplug_remove,
  436. };
  437. module_platform_driver(mlxcpld_hotplug_driver);
  438. MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
  439. MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver");
  440. MODULE_LICENSE("Dual BSD/GPL");
  441. MODULE_ALIAS("platform:mlxcpld-hotplug");