stpddc60.c 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Hardware monitoring driver for the STPDDC60 controller
  4. *
  5. * Copyright (c) 2021 Flextronics International Sweden AB.
  6. */
  7. #include <linux/kernel.h>
  8. #include <linux/module.h>
  9. #include <linux/init.h>
  10. #include <linux/err.h>
  11. #include <linux/i2c.h>
  12. #include <linux/pmbus.h>
  13. #include "pmbus.h"
  14. #define STPDDC60_MFR_READ_VOUT 0xd2
  15. #define STPDDC60_MFR_OV_LIMIT_OFFSET 0xe5
  16. #define STPDDC60_MFR_UV_LIMIT_OFFSET 0xe6
  17. static const struct i2c_device_id stpddc60_id[] = {
  18. {"stpddc60", 0},
  19. {"bmr481", 0},
  20. {}
  21. };
  22. MODULE_DEVICE_TABLE(i2c, stpddc60_id);
  23. static struct pmbus_driver_info stpddc60_info = {
  24. .pages = 1,
  25. .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
  26. | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
  27. | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP
  28. | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
  29. | PMBUS_HAVE_POUT,
  30. };
  31. /*
  32. * Calculate the closest absolute offset between commanded vout value
  33. * and limit value in steps of 50mv in the range 0 (50mv) to 7 (400mv).
  34. * Return 0 if the upper limit is lower than vout or if the lower limit
  35. * is higher than vout.
  36. */
  37. static u8 stpddc60_get_offset(int vout, u16 limit, bool over)
  38. {
  39. int offset;
  40. long v, l;
  41. v = 250 + (vout - 1) * 5; /* Convert VID to mv */
  42. l = (limit * 1000L) >> 8; /* Convert LINEAR to mv */
  43. if (over == (l < v))
  44. return 0;
  45. offset = DIV_ROUND_CLOSEST(abs(l - v), 50);
  46. if (offset > 0)
  47. offset--;
  48. return clamp_val(offset, 0, 7);
  49. }
  50. /*
  51. * Adjust the linear format word to use the given fixed exponent.
  52. */
  53. static u16 stpddc60_adjust_linear(u16 word, s16 fixed)
  54. {
  55. s16 e, m, d;
  56. e = ((s16)word) >> 11;
  57. m = ((s16)((word & 0x7ff) << 5)) >> 5;
  58. d = e - fixed;
  59. if (d >= 0)
  60. m <<= d;
  61. else
  62. m >>= -d;
  63. return clamp_val(m, 0, 0x3ff) | ((fixed << 11) & 0xf800);
  64. }
  65. /*
  66. * The VOUT_COMMAND register uses the VID format but the vout alarm limit
  67. * registers use the LINEAR format so we override VOUT_MODE here to force
  68. * LINEAR format for all registers.
  69. */
  70. static int stpddc60_read_byte_data(struct i2c_client *client, int page, int reg)
  71. {
  72. int ret;
  73. if (page > 0)
  74. return -ENXIO;
  75. switch (reg) {
  76. case PMBUS_VOUT_MODE:
  77. ret = 0x18;
  78. break;
  79. default:
  80. ret = -ENODATA;
  81. break;
  82. }
  83. return ret;
  84. }
  85. /*
  86. * The vout related registers return values in LINEAR11 format when LINEAR16
  87. * is expected. Clear the top 5 bits to set the exponent part to zero to
  88. * convert the value to LINEAR16 format.
  89. */
  90. static int stpddc60_read_word_data(struct i2c_client *client, int page,
  91. int phase, int reg)
  92. {
  93. int ret;
  94. if (page > 0)
  95. return -ENXIO;
  96. switch (reg) {
  97. case PMBUS_READ_VOUT:
  98. ret = pmbus_read_word_data(client, page, phase,
  99. STPDDC60_MFR_READ_VOUT);
  100. if (ret < 0)
  101. return ret;
  102. ret &= 0x7ff;
  103. break;
  104. case PMBUS_VOUT_OV_FAULT_LIMIT:
  105. case PMBUS_VOUT_UV_FAULT_LIMIT:
  106. ret = pmbus_read_word_data(client, page, phase, reg);
  107. if (ret < 0)
  108. return ret;
  109. ret &= 0x7ff;
  110. break;
  111. default:
  112. ret = -ENODATA;
  113. break;
  114. }
  115. return ret;
  116. }
  117. /*
  118. * The vout under- and over-voltage limits are set as an offset relative to
  119. * the commanded vout voltage. The vin, iout, pout and temp limits must use
  120. * the same fixed exponent the chip uses to encode the data when read.
  121. */
  122. static int stpddc60_write_word_data(struct i2c_client *client, int page,
  123. int reg, u16 word)
  124. {
  125. int ret;
  126. u8 offset;
  127. if (page > 0)
  128. return -ENXIO;
  129. switch (reg) {
  130. case PMBUS_VOUT_OV_FAULT_LIMIT:
  131. ret = pmbus_read_word_data(client, page, 0xff,
  132. PMBUS_VOUT_COMMAND);
  133. if (ret < 0)
  134. return ret;
  135. offset = stpddc60_get_offset(ret, word, true);
  136. ret = pmbus_write_byte_data(client, page,
  137. STPDDC60_MFR_OV_LIMIT_OFFSET,
  138. offset);
  139. break;
  140. case PMBUS_VOUT_UV_FAULT_LIMIT:
  141. ret = pmbus_read_word_data(client, page, 0xff,
  142. PMBUS_VOUT_COMMAND);
  143. if (ret < 0)
  144. return ret;
  145. offset = stpddc60_get_offset(ret, word, false);
  146. ret = pmbus_write_byte_data(client, page,
  147. STPDDC60_MFR_UV_LIMIT_OFFSET,
  148. offset);
  149. break;
  150. case PMBUS_VIN_OV_FAULT_LIMIT:
  151. case PMBUS_VIN_UV_FAULT_LIMIT:
  152. case PMBUS_OT_FAULT_LIMIT:
  153. case PMBUS_OT_WARN_LIMIT:
  154. case PMBUS_IOUT_OC_FAULT_LIMIT:
  155. case PMBUS_IOUT_OC_WARN_LIMIT:
  156. case PMBUS_POUT_OP_FAULT_LIMIT:
  157. ret = pmbus_read_word_data(client, page, 0xff, reg);
  158. if (ret < 0)
  159. return ret;
  160. word = stpddc60_adjust_linear(word, ret >> 11);
  161. ret = pmbus_write_word_data(client, page, reg, word);
  162. break;
  163. default:
  164. ret = -ENODATA;
  165. break;
  166. }
  167. return ret;
  168. }
  169. static int stpddc60_probe(struct i2c_client *client)
  170. {
  171. int status;
  172. u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
  173. const struct i2c_device_id *mid;
  174. struct pmbus_driver_info *info = &stpddc60_info;
  175. if (!i2c_check_functionality(client->adapter,
  176. I2C_FUNC_SMBUS_READ_BYTE_DATA
  177. | I2C_FUNC_SMBUS_BLOCK_DATA))
  178. return -ENODEV;
  179. status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
  180. if (status < 0) {
  181. dev_err(&client->dev, "Failed to read Manufacturer Model\n");
  182. return status;
  183. }
  184. for (mid = stpddc60_id; mid->name[0]; mid++) {
  185. if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
  186. break;
  187. }
  188. if (!mid->name[0]) {
  189. dev_err(&client->dev, "Unsupported device\n");
  190. return -ENODEV;
  191. }
  192. info->read_byte_data = stpddc60_read_byte_data;
  193. info->read_word_data = stpddc60_read_word_data;
  194. info->write_word_data = stpddc60_write_word_data;
  195. status = pmbus_do_probe(client, info);
  196. if (status < 0)
  197. return status;
  198. pmbus_set_update(client, PMBUS_VOUT_OV_FAULT_LIMIT, true);
  199. pmbus_set_update(client, PMBUS_VOUT_UV_FAULT_LIMIT, true);
  200. return 0;
  201. }
  202. static struct i2c_driver stpddc60_driver = {
  203. .driver = {
  204. .name = "stpddc60",
  205. },
  206. .probe_new = stpddc60_probe,
  207. .id_table = stpddc60_id,
  208. };
  209. module_i2c_driver(stpddc60_driver);
  210. MODULE_AUTHOR("Erik Rosen <[email protected]>");
  211. MODULE_DESCRIPTION("PMBus driver for ST STPDDC60");
  212. MODULE_LICENSE("GPL");
  213. MODULE_IMPORT_NS(PMBUS);