diff mbox series

hwmon: add fan/pwm driver for corsair h100i platinum

Message ID 20200714100337.48719-1-jaap.aarts1@gmail.com (mailing list archive)
State Changes Requested
Headers show
Series hwmon: add fan/pwm driver for corsair h100i platinum | expand

Commit Message

jaap aarts July 14, 2020, 10:03 a.m. UTC
Adds fan/pwm support for H1000i platinum.
Custom temp/fan curves are not supported, however
the presets found in the proprietary drivers are avaiable.

Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
---
 drivers/hwmon/Kconfig       |   6 +
 drivers/hwmon/Makefile      |   1 +
 drivers/hwmon/asetek_gen6.c | 801 ++++++++++++++++++++++++++++++++++++
 3 files changed, 808 insertions(+)
 create mode 100644 drivers/hwmon/asetek_gen6.c

Comments

kernel test robot July 14, 2020, 11:01 p.m. UTC | #1
Hi jaap,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on hwmon/hwmon-next]
[also build test ERROR on v5.8-rc5 next-20200714]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use  as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/jaap-aarts/hwmon-add-fan-pwm-driver-for-corsair-h100i-platinum/20200714-180650
base:   https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next
config: m68k-allmodconfig (attached as .config)
compiler: m68k-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=m68k 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

   In file included from arch/m68k/include/asm/io_mm.h:25,
                    from arch/m68k/include/asm/io.h:8,
                    from include/linux/io.h:13,
                    from include/linux/irq.h:20,
                    from include/asm-generic/hardirq.h:13,
                    from ./arch/m68k/include/generated/asm/hardirq.h:1,
                    from include/linux/hardirq.h:10,
                    from include/linux/interrupt.h:11,
                    from include/linux/usb.h:16,
                    from drivers/hwmon/asetek_gen6.c:23:
   arch/m68k/include/asm/raw_io.h: In function 'raw_rom_outsb':
   arch/m68k/include/asm/raw_io.h:83:7: warning: variable '__w' set but not used [-Wunused-but-set-variable]
      83 |  ({u8 __w, __v = (b);  u32 _addr = ((u32) (addr)); \
         |       ^~~
   arch/m68k/include/asm/raw_io.h:430:3: note: in expansion of macro 'rom_out_8'
     430 |   rom_out_8(port, *buf++);
         |   ^~~~~~~~~
   arch/m68k/include/asm/raw_io.h: In function 'raw_rom_outsw':
   arch/m68k/include/asm/raw_io.h:86:8: warning: variable '__w' set but not used [-Wunused-but-set-variable]
      86 |  ({u16 __w, __v = (w); u32 _addr = ((u32) (addr)); \
         |        ^~~
   arch/m68k/include/asm/raw_io.h:448:3: note: in expansion of macro 'rom_out_be16'
     448 |   rom_out_be16(port, *buf++);
         |   ^~~~~~~~~~~~
   arch/m68k/include/asm/raw_io.h: In function 'raw_rom_outsw_swapw':
   arch/m68k/include/asm/raw_io.h:90:8: warning: variable '__w' set but not used [-Wunused-but-set-variable]
      90 |  ({u16 __w, __v = (w); u32 _addr = ((u32) (addr)); \
         |        ^~~
   arch/m68k/include/asm/raw_io.h:466:3: note: in expansion of macro 'rom_out_le16'
     466 |   rom_out_le16(port, *buf++);
         |   ^~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:174:5: warning: no previous prototype for 'set_fan_rpm_curve' [-Wmissing-prototypes]
     174 | int set_fan_rpm_curve(struct driver *cdev, struct hwmon_fan_data *fan_data,
         |     ^~~~~~~~~~~~~~~~~
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   drivers/hwmon/asetek_gen6.c: In function 'set_fan_rpm_curve':
>> drivers/hwmon/asetek_gen6.c:212:12: error: passing argument 1 of '_dev_info' from incompatible pointer type [-Werror=incompatible-pointer-types]
     212 |   dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
         |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         |            |
         |            char *
   include/linux/dev_printk.h:110:12: note: in definition of macro 'dev_info'
     110 |  _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |            ^~~
   include/linux/dev_printk.h:48:37: note: expected 'const struct device *' but argument is of type 'char *'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                ~~~~~~~~~~~~~~~~~~~~~^~~
>> drivers/hwmon/asetek_gen6.c:213:13: warning: passing argument 2 of '_dev_info' makes pointer from integer without a cast [-Wint-conversion]
     213 |     recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
         |     ~~~~~~~~^~~
         |             |
         |             char
   include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt'
      19 | #define dev_fmt(fmt) fmt
         |                      ^~~
   drivers/hwmon/asetek_gen6.c:212:3: note: in expansion of macro 'dev_info'
     212 |   dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
         |   ^~~~~~~~
   include/linux/dev_printk.h:48:54: note: expected 'const char *' but argument is of type 'char'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                                          ~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:217:5: warning: no previous prototype for 'set_fan_target_rpm' [-Wmissing-prototypes]
     217 | int set_fan_target_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
         |     ^~~~~~~~~~~~~~~~~~
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   drivers/hwmon/asetek_gen6.c: In function 'set_fan_target_rpm':
   drivers/hwmon/asetek_gen6.c:246:12: error: passing argument 1 of '_dev_info' from incompatible pointer type [-Werror=incompatible-pointer-types]
     246 |   dev_info("[*] Failled setting fan rpm %d,%d,%d/%d\n",
         |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         |            |
         |            char *
   include/linux/dev_printk.h:110:12: note: in definition of macro 'dev_info'
     110 |  _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |            ^~~
   include/linux/dev_printk.h:48:37: note: expected 'const struct device *' but argument is of type 'char *'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                ~~~~~~~~~~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c:247:13: warning: passing argument 2 of '_dev_info' makes pointer from integer without a cast [-Wint-conversion]
     247 |     recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
         |     ~~~~~~~~^~~
         |             |
         |             char
   include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt'
      19 | #define dev_fmt(fmt) fmt
         |                      ^~~
   drivers/hwmon/asetek_gen6.c:246:3: note: in expansion of macro 'dev_info'
     246 |   dev_info("[*] Failled setting fan rpm %d,%d,%d/%d\n",
         |   ^~~~~~~~
   include/linux/dev_printk.h:48:54: note: expected 'const char *' but argument is of type 'char'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                                          ~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:251:6: warning: no previous prototype for 'get_fan_target_rpm' [-Wmissing-prototypes]
     251 | void get_fan_target_rpm(struct hwmon_fan_data *fan_data, long *val)
         |      ^~~~~~~~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c:256:5: warning: no previous prototype for 'get_fan_current_rpm' [-Wmissing-prototypes]
     256 | int get_fan_current_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
         |     ^~~~~~~~~~~~~~~~~~~
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   drivers/hwmon/asetek_gen6.c: In function 'get_fan_current_rpm':
   drivers/hwmon/asetek_gen6.c:280:12: error: passing argument 1 of '_dev_info' from incompatible pointer type [-Werror=incompatible-pointer-types]
     280 |   dev_info("[*] Failled retrieving fan rmp %d,%d,%d/%d\n",
         |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         |            |
         |            char *
   include/linux/dev_printk.h:110:12: note: in definition of macro 'dev_info'
     110 |  _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |            ^~~
   include/linux/dev_printk.h:48:37: note: expected 'const struct device *' but argument is of type 'char *'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                ~~~~~~~~~~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c:281:13: warning: passing argument 2 of '_dev_info' makes pointer from integer without a cast [-Wint-conversion]
     281 |     recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
         |     ~~~~~~~~^~~
         |             |
         |             char
   include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt'
      19 | #define dev_fmt(fmt) fmt
         |                      ^~~
   drivers/hwmon/asetek_gen6.c:280:3: note: in expansion of macro 'dev_info'
     280 |   dev_info("[*] Failled retrieving fan rmp %d,%d,%d/%d\n",
         |   ^~~~~~~~
   include/linux/dev_printk.h:48:54: note: expected 'const char *' but argument is of type 'char'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                                          ~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:287:5: warning: no previous prototype for 'set_fan_target_pwm' [-Wmissing-prototypes]
     287 | int set_fan_target_pwm(struct driver *cdev, struct hwmon_fan_data *fan_data,
         |     ^~~~~~~~~~~~~~~~~~
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   drivers/hwmon/asetek_gen6.c: In function 'set_fan_target_pwm':
   drivers/hwmon/asetek_gen6.c:315:12: error: passing argument 1 of '_dev_info' from incompatible pointer type [-Werror=incompatible-pointer-types]
     315 |   dev_info("[*] Failled setting fan pwm %d,%d,%d/%d\n",
         |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         |            |
         |            char *
   include/linux/dev_printk.h:110:12: note: in definition of macro 'dev_info'
     110 |  _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)
         |            ^~~
   include/linux/dev_printk.h:48:37: note: expected 'const struct device *' but argument is of type 'char *'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                ~~~~~~~~~~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c:316:13: warning: passing argument 2 of '_dev_info' makes pointer from integer without a cast [-Wint-conversion]
     316 |     recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
         |     ~~~~~~~~^~~
         |             |
         |             unsigned char
   include/linux/dev_printk.h:19:22: note: in definition of macro 'dev_fmt'
      19 | #define dev_fmt(fmt) fmt
         |                      ^~~
   drivers/hwmon/asetek_gen6.c:315:3: note: in expansion of macro 'dev_info'
     315 |   dev_info("[*] Failled setting fan pwm %d,%d,%d/%d\n",
         |   ^~~~~~~~
   include/linux/dev_printk.h:48:54: note: expected 'const char *' but argument is of type 'unsigned char'
      48 | void _dev_info(const struct device *dev, const char *fmt, ...);
         |                                          ~~~~~~~~~~~~^~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:320:9: warning: no previous prototype for 'is_visible_func' [-Wmissing-prototypes]
     320 | umode_t is_visible_func(const void *d, enum hwmon_sensor_types type, u32 attr,
         |         ^~~~~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c:475:5: warning: no previous prototype for 'read_func' [-Wmissing-prototypes]
     475 | int read_func(struct device *dev, enum hwmon_sensor_types type, u32 attr,
         |     ^~~~~~~~~
   drivers/hwmon/asetek_gen6.c:556:6: warning: no previous prototype for 'does_fan_exist' [-Wmissing-prototypes]
     556 | bool does_fan_exist(struct driver *cdev, int channel)
         |      ^~~~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c: In function 'does_fan_exist':
   drivers/hwmon/asetek_gen6.c:569:16: warning: overflow in conversion from 'int' to 'char' changes value from '600' to '88' [-Woverflow]
     569 |  send_buf[3] = 600;
         |                ^~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:584:5: warning: no previous prototype for 'get_fan_count' [-Wmissing-prototypes]
     584 | int get_fan_count(struct driver *dev)
         |     ^~~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c:593:6: warning: no previous prototype for 'hwmon_init' [-Wmissing-prototypes]
     593 | void hwmon_init(struct driver *dev)
         |      ^~~~~~~~~~
   drivers/hwmon/asetek_gen6.c: In function 'hwmon_init':
>> drivers/hwmon/asetek_gen6.c:650:30: error: macro "dev_info" requires 3 arguments, but only 1 given
     650 |  dev_info("[*] Setup hwmon\n");
         |                              ^
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   include/linux/dev_printk.h:109: note: macro "dev_info" defined here
     109 | #define dev_info(dev, fmt, ...)      \
         | 
>> drivers/hwmon/asetek_gen6.c:650:2: error: 'dev_info' undeclared (first use in this function); did you mean '_dev_info'?
     650 |  dev_info("[*] Setup hwmon\n");
         |  ^~~~~~~~
         |  _dev_info
   drivers/hwmon/asetek_gen6.c:650:2: note: each undeclared identifier is reported only once for each function it appears in
   drivers/hwmon/asetek_gen6.c:596:17: warning: variable 'hwmon_dev' set but not used [-Wunused-but-set-variable]
     596 |  struct device *hwmon_dev;
         |                 ^~~~~~~~~
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:653:6: warning: no previous prototype for 'hwmon_deinit' [-Wmissing-prototypes]
     653 | void hwmon_deinit(struct driver *dev)
         |      ^~~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c: In function 'hwmon_deinit':
   drivers/hwmon/asetek_gen6.c:656:35: error: macro "dev_info" requires 3 arguments, but only 1 given
     656 |  dev_info("[*] HWMON DISCONNECT\n");
         |                                   ^
   In file included from include/linux/device.h:15,
                    from include/linux/usb/ch9.h:36,
                    from include/linux/usb.h:6,
                    from drivers/hwmon/asetek_gen6.c:23:
   include/linux/dev_printk.h:109: note: macro "dev_info" defined here
     109 | #define dev_info(dev, fmt, ...)      \
         | 
   drivers/hwmon/asetek_gen6.c:656:2: error: 'dev_info' undeclared (first use in this function); did you mean '_dev_info'?
     656 |  dev_info("[*] HWMON DISCONNECT\n");
         |  ^~~~~~~~
         |  _dev_info
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:670:5: warning: no previous prototype for 'init_device' [-Wmissing-prototypes]
     670 | int init_device(struct usb_device *udev)
         |     ^~~~~~~~~~~
   drivers/hwmon/asetek_gen6.c: In function 'init_device':
   drivers/hwmon/asetek_gen6.c:678:3: warning: suggest braces around empty body in an 'if' statement [-Wempty-body]
     678 |   ;
         |   ^
   drivers/hwmon/asetek_gen6.c: At top level:
   drivers/hwmon/asetek_gen6.c:688:5: warning: no previous prototype for 'deinit_device' [-Wmissing-prototypes]
     688 | int deinit_device(struct usb_device *udev)
         |     ^~~~~~~~~~~~~
   cc1: some warnings being treated as errors

vim +/_dev_info +212 drivers/hwmon/asetek_gen6.c

  > 23	#include <linux/usb.h>
    24	#include <linux/slab.h>
    25	#include <linux/mutex.h>
    26	#include <linux/errno.h>
    27	#include <linux/hwmon.h>
    28	#include <linux/hwmon-sysfs.h>
    29	
    30	struct driver {
    31		struct usb_device *udev;
    32	
    33		char *bulk_out_buffer;
    34		char *bulk_in_buffer;
    35		size_t bulk_out_size;
    36		size_t bulk_in_size;
    37		char bulk_in_endpointAddr;
    38		char bulk_out_endpointAddr;
    39	
    40		struct usb_interface *interface; /* the interface for this device */
    41		struct mutex io_mutex; /* synchronize I/O with disconnect */
    42		struct semaphore
    43			limit_sem; /* limiting the number of writes in progress */
    44	
    45		struct kref kref;
    46	};
    47	
    48	struct curve_point {
    49		uint8_t temp;
    50		uint8_t pwm;
    51	};
    52	
    53	struct hwmon_fan_data {
    54		char fan_channel;
    55		long fan_target;
    56		unsigned char fan_pwm_target;
    57		long mode;
    58		struct curve_point curve[7];
    59	};
    60	
    61	struct hwmon_data {
    62		struct driver *dev;
    63		int channel_count;
    64		void **channel_data;
    65	};
    66	
    67	struct curve_point quiet_curve[] = {
    68		{
    69			.temp = 0x1F,
    70			.pwm = 0x15,
    71		},
    72		{
    73			.temp = 0x21,
    74			.pwm = 0x1E,
    75		},
    76		{
    77			.temp = 0x24,
    78			.pwm = 0x25,
    79		},
    80		{
    81			.temp = 0x27,
    82			.pwm = 0x2D,
    83		},
    84		{
    85			.temp = 0x29,
    86			.pwm = 0x38,
    87		},
    88		{
    89			.temp = 0x2C,
    90			.pwm = 0x4A,
    91		},
    92		{
    93			.temp = 0x2F,
    94			.pwm = 0x64,
    95		},
    96	};
    97	
    98	struct curve_point balanced_curve[] = {
    99		{
   100			.temp = 0x1C,
   101			.pwm = 0x15,
   102		},
   103		{
   104			.temp = 0x1E,
   105			.pwm = 0x1B,
   106		},
   107		{
   108			.temp = 0x20,
   109			.pwm = 0x23,
   110		},
   111		{
   112			.temp = 0x22,
   113			.pwm = 0x28,
   114		},
   115		{
   116			.temp = 0x24,
   117			.pwm = 0x32,
   118		},
   119		{
   120			.temp = 0x27,
   121			.pwm = 0x48,
   122		},
   123		{
   124			.temp = 0x29,
   125			.pwm = 0x64,
   126		},
   127	};
   128	
   129	struct curve_point extreme_curve[] = {
   130		{
   131			.temp = 0x19,
   132			.pwm = 0x28,
   133		},
   134		{
   135			.temp = 0x1B,
   136			.pwm = 0x2E,
   137		},
   138		{
   139			.temp = 0x1D,
   140			.pwm = 0x37,
   141		},
   142		{
   143			.temp = 0x1E,
   144			.pwm = 0x41,
   145		},
   146		{
   147			.temp = 0x1F,
   148			.pwm = 0x4C,
   149		},
   150		{
   151			.temp = 0x20,
   152			.pwm = 0x56,
   153		},
   154		{
   155			.temp = 0x21,
   156			.pwm = 0x64,
   157		},
   158	};
   159	
   160	#define default_curve quiet_curve
   161	
   162	static const char SUCCESS[2] = { 0x12, 0x34 };
   163	
   164	#define SUCCES_LENGTH 3
   165	
   166	static bool check_succes(char command, char ret[SUCCES_LENGTH])
   167	{
   168		char success[SUCCES_LENGTH] = { command };
   169	
   170		strncpy(&success[1], SUCCESS, SUCCES_LENGTH - 1);
   171		return strncmp(ret, success, SUCCES_LENGTH - 1) == 0;
   172	}
   173	
 > 174	int set_fan_rpm_curve(struct driver *cdev, struct hwmon_fan_data *fan_data,
   175			      struct curve_point point[7])
   176	{
   177		int retval;
   178		int wrote;
   179		int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
   180		int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
   181		char *send_buf = cdev->bulk_out_buffer;
   182		char *recv_buf = cdev->bulk_in_buffer;
   183	
   184		memcpy(fan_data->curve, point, sizeof(fan_data->curve));
   185	
   186		send_buf[0] = 0x40;
   187		send_buf[1] = fan_data->fan_channel;
   188		send_buf[2] = point[0].temp;
   189		send_buf[3] = point[1].temp;
   190		send_buf[4] = point[2].temp;
   191		send_buf[5] = point[3].temp;
   192		send_buf[6] = point[4].temp;
   193		send_buf[7] = point[5].temp;
   194		send_buf[8] = point[6].temp;
   195		send_buf[9] = point[0].pwm;
   196		send_buf[10] = point[1].pwm;
   197		send_buf[11] = point[2].pwm;
   198		send_buf[12] = point[3].pwm;
   199		send_buf[13] = point[4].pwm;
   200		send_buf[14] = point[5].pwm;
   201		send_buf[15] = point[6].pwm;
   202	
   203		retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 16, &wrote, 100);
   204		if (retval != 0)
   205			return retval;
   206	
   207		retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 4, &wrote, 100);
   208		if (retval != 0)
   209			return retval;
   210	
   211		if (!check_succes(send_buf[0], recv_buf))
 > 212			dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
 > 213				 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
   214		return 0;
   215	}
   216	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Guenter Roeck July 15, 2020, 3:07 a.m. UTC | #2
On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> Adds fan/pwm support for H1000i platinum.
> Custom temp/fan curves are not supported, however
> the presets found in the proprietary drivers are avaiable.
> 
> Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>

+Marius Zachmann for input.

Questions:
- Does this really have to be a different driver or can it be merged into
  the corsair-cpro driver ?
- What about HID vs. USB driver ?
- is the kref complexity really needed ?

> ---
>  drivers/hwmon/Kconfig       |   6 +
>  drivers/hwmon/Makefile      |   1 +
>  drivers/hwmon/asetek_gen6.c | 801 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 808 insertions(+)
>  create mode 100644 drivers/hwmon/asetek_gen6.c
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 288ae9f63588..521a9e0c88ca 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -378,6 +378,12 @@ config SENSORS_ARM_SCPI
>  	  and power sensors available on ARM Ltd's SCP based platforms. The
>  	  actual number and type of sensors exported depend on the platform.
>  
> +config SENSORS_ASETEK_GEN6
> +	tristate "Asetek generation 6 driver"
> +	help
> +	  If you say yes here you get support for asetek generation 6 boards
> +	  found on most AIO liquid coolers with an asetek pump.
> +
>  config SENSORS_ASB100
>  	tristate "Asus ASB100 Bach"
>  	depends on X86 && I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 3e32c21f5efe..9683d2aae2b2 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -20,6 +20,7 @@ obj-$(CONFIG_SENSORS_W83793)	+= w83793.o
>  obj-$(CONFIG_SENSORS_W83795)	+= w83795.o
>  obj-$(CONFIG_SENSORS_W83781D)	+= w83781d.o
>  obj-$(CONFIG_SENSORS_W83791D)	+= w83791d.o
> +obj-$(CONFIG_SENSORS_ASETEK_GEN6)	+= asetek_gen6.o
>  
>  obj-$(CONFIG_SENSORS_AB8500)	+= abx500.o ab8500.o
>  obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
> diff --git a/drivers/hwmon/asetek_gen6.c b/drivers/hwmon/asetek_gen6.c
> new file mode 100644
> index 000000000000..4aea9ae0b944
> --- /dev/null
> +++ b/drivers/hwmon/asetek_gen6.c
> @@ -0,0 +1,801 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * A hwmon driver for most asetek gen 6 all-in-one liquid coolers.
> + * Copyright (c) Jaap Aarts 2020
> + * 
> + * Protocol reverse engineered by audiohacked
> + * https://github.com/audiohacked/OpendriverLink
> + */
> +
> +/*
> + * Supports following chips:

This isn't really a chip, it is a liquid cooler.

> + * h100i platinum
> + * 
> + * Other products should work with this driver but no testing has been done.
> + * 
> + * Note: platinum is the codename name for pro within driver, so h100i platinum = h1ooi pro

h1ooi ? o's instead of 0 ?

> + * 
> + * Note: fan curve control has not been implemented
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/usb.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/errno.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>

Alphabetic order, please. Also, it doesn't look like hwmon-sysfs.h is used.

> +
> +struct driver {

this is a terribly generic name for a local structure.

> +	struct usb_device *udev;
> +
> +	char *bulk_out_buffer;
> +	char *bulk_in_buffer;
> +	size_t bulk_out_size;
> +	size_t bulk_in_size;
> +	char bulk_in_endpointAddr;
> +	char bulk_out_endpointAddr;
> +
> +	struct usb_interface *interface; /* the interface for this device */
> +	struct mutex io_mutex; /* synchronize I/O with disconnect */

Not used anywhere.

> +	struct semaphore
> +		limit_sem; /* limiting the number of writes in progress */

I don't see the need for a semaphore in this driver.

> +
> +	struct kref kref;
> +};
> +
> +struct curve_point {
> +	uint8_t temp;
> +	uint8_t pwm;
> +};
> +
> +struct hwmon_fan_data {
> +	char fan_channel;
> +	long fan_target;
> +	unsigned char fan_pwm_target;
> +	long mode;
> +	struct curve_point curve[7];
> +};
> +
> +struct hwmon_data {
> +	struct driver *dev;
> +	int channel_count;
> +	void **channel_data;
> +};
> +
> +struct curve_point quiet_curve[] = {
> +	{
> +		.temp = 0x1F,
> +		.pwm = 0x15,
> +	},
> +	{
> +		.temp = 0x21,
> +		.pwm = 0x1E,
> +	},
> +	{
> +		.temp = 0x24,
> +		.pwm = 0x25,
> +	},
> +	{
> +		.temp = 0x27,
> +		.pwm = 0x2D,
> +	},
> +	{
> +		.temp = 0x29,
> +		.pwm = 0x38,
> +	},
> +	{
> +		.temp = 0x2C,
> +		.pwm = 0x4A,
> +	},
> +	{
> +		.temp = 0x2F,
> +		.pwm = 0x64,
> +	},
> +};
> +
> +struct curve_point balanced_curve[] = {
> +	{
> +		.temp = 0x1C,
> +		.pwm = 0x15,
> +	},
> +	{
> +		.temp = 0x1E,
> +		.pwm = 0x1B,
> +	},
> +	{
> +		.temp = 0x20,
> +		.pwm = 0x23,
> +	},
> +	{
> +		.temp = 0x22,
> +		.pwm = 0x28,
> +	},
> +	{
> +		.temp = 0x24,
> +		.pwm = 0x32,
> +	},
> +	{
> +		.temp = 0x27,
> +		.pwm = 0x48,
> +	},
> +	{
> +		.temp = 0x29,
> +		.pwm = 0x64,
> +	},
> +};
> +
> +struct curve_point extreme_curve[] = {
> +	{
> +		.temp = 0x19,
> +		.pwm = 0x28,
> +	},
> +	{
> +		.temp = 0x1B,
> +		.pwm = 0x2E,
> +	},
> +	{
> +		.temp = 0x1D,
> +		.pwm = 0x37,
> +	},
> +	{
> +		.temp = 0x1E,
> +		.pwm = 0x41,
> +	},
> +	{
> +		.temp = 0x1F,
> +		.pwm = 0x4C,
> +	},
> +	{
> +		.temp = 0x20,
> +		.pwm = 0x56,
> +	},
> +	{
> +		.temp = 0x21,
> +		.pwm = 0x64,
> +	},
> +};

We don't get into the business of deciding fan curves in the kernel.
The driver should implement sets of {pwm_auto_pointN_temp,
pwm_auto_pointN_pwm} instead and leave it up to userspace to set
actual values.

> +
> +#define default_curve quiet_curve
> +
> +static const char SUCCESS[2] = { 0x12, 0x34 };
> +
> +#define SUCCES_LENGTH 3
> +
> +static bool check_succes(char command, char ret[SUCCES_LENGTH])
> +{
> +	char success[SUCCES_LENGTH] = { command };
> +
> +	strncpy(&success[1], SUCCESS, SUCCES_LENGTH - 1);
> +	return strncmp(ret, success, SUCCES_LENGTH - 1) == 0;
> +}

This seems terribly expensive and complicated. I have not spend time trying
to analyze what it actually does, but it seems highly unlikely that such a
complex evaluation is needed.

> +
> +int set_fan_rpm_curve(struct driver *cdev, struct hwmon_fan_data *fan_data,
> +		      struct curve_point point[7])
> +{
> +	int retval;
> +	int wrote;
> +	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> +	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> +	char *send_buf = cdev->bulk_out_buffer;
> +	char *recv_buf = cdev->bulk_in_buffer;
> +
> +	memcpy(fan_data->curve, point, sizeof(fan_data->curve));
> +
> +	send_buf[0] = 0x40;
> +	send_buf[1] = fan_data->fan_channel;
> +	send_buf[2] = point[0].temp;
> +	send_buf[3] = point[1].temp;
> +	send_buf[4] = point[2].temp;
> +	send_buf[5] = point[3].temp;
> +	send_buf[6] = point[4].temp;
> +	send_buf[7] = point[5].temp;
> +	send_buf[8] = point[6].temp;
> +	send_buf[9] = point[0].pwm;
> +	send_buf[10] = point[1].pwm;
> +	send_buf[11] = point[2].pwm;
> +	send_buf[12] = point[3].pwm;
> +	send_buf[13] = point[4].pwm;
> +	send_buf[14] = point[5].pwm;
> +	send_buf[15] = point[6].pwm;
> +
> +	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 16, &wrote, 100);
> +	if (retval != 0)

	if (retval)
> +		return retval;
> +
> +	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 4, &wrote, 100);
> +	if (retval != 0)
> +		return retval;
> +
> +	if (!check_succes(send_buf[0], recv_buf))
> +		dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
> +			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);

No error return ? Then drop the check.

> +	return 0;
> +}
> +
> +int set_fan_target_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> +		       long val)
> +{
> +	int retval;
> +	int wrote;
> +	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> +	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> +
> +	char *send_buf = cdev->bulk_out_buffer;
> +	char *recv_buf = cdev->bulk_in_buffer;
> +
> +	fan_data->fan_target = val;
> +	fan_data->fan_pwm_target = 0;
> +
> +	send_buf[0] = 0x43;

Please use defines instead of magic values.

> +	send_buf[1] = fan_data->fan_channel;
> +	send_buf[2] = (fan_data->fan_target >> 8);
> +	send_buf[3] = fan_data->fan_target;
> +
> +	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> +	if (retval != 0)
> +		return retval;
> +
> +	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);

100 seconds timeout is a bit unreal.

> +	if (retval != 0)
> +		return retval;
> +
> +	//no error

Useless comment.

> +	if (!check_succes(send_buf[0], recv_buf))
> +		dev_info("[*] Failled setting fan rpm %d,%d,%d/%d\n",
> +			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> +	return 0;
> +}
> +
> +void get_fan_target_rpm(struct hwmon_fan_data *fan_data, long *val)
> +{
> +	*val = fan_data->fan_target;
> +}

This function does not add any value. Caller can access fan_data->fan_target
directly.

> +
> +int get_fan_current_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> +			long *val)
> +{
> +	int retval;
> +	int wrote;
> +	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> +	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> +
> +	char *send_buf = cdev->bulk_out_buffer;
> +	char *recv_buf = cdev->bulk_in_buffer;
> +
> +	send_buf[0] = 0x41;
> +	send_buf[1] = fan_data->fan_channel;
> +
> +	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 2, &wrote, 100);
> +	if (retval != 0)
> +		return retval;
> +
> +	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100);
> +	if (retval != 0)
> +		return retval;
> +
> +	if (!check_succes(0x41, recv_buf) ||
> +	    recv_buf[3] != fan_data->fan_channel)
> +		dev_info("[*] Failled retrieving fan rmp %d,%d,%d/%d\n",

Failed, rpm. But that message is really pointless (as is the check),
since the result is ignored and whatever is in the buffer is returned
to the caller anyway.

> +			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> +
> +	*val = (((uint8_t)recv_buf[4]) << 8) + (uint8_t)recv_buf[5];

Declaring send_buf and recv_buf as char pointer just to typecast recv_buf
is not really useful and just makes the code more complex.

> +	return 0;
> +}
> +
> +int set_fan_target_pwm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> +		       long val)
> +{
> +	int retval;
> +	int wrote;
> +	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> +	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> +
> +	unsigned char *send_buf = cdev->bulk_out_buffer;
> +	unsigned char *recv_buf = cdev->bulk_in_buffer;

Now we have char, uint8_t, and unsigned char. Please be consistent.

> +
> +	fan_data->fan_pwm_target = val;
> +	fan_data->fan_target = 0;
> +
> +	send_buf[0] = 0x42;
> +	send_buf[1] = fan_data->fan_channel;
> +	send_buf[3] = fan_data->fan_pwm_target;
> +
> +	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> +	if (retval != 0)
> +		return retval;
> +
> +	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
> +	if (retval != 0)
> +		return retval;
> +
> +	//no error
> +	if (!check_succes(send_buf[0], recv_buf))
> +		dev_info("[*] Failled setting fan pwm %d,%d,%d/%d\n",
> +			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> +	return 0;
> +}
> +
> +umode_t is_visible_func(const void *d, enum hwmon_sensor_types type, u32 attr,

_func is quite pointless in function names.

> +			int channel)

Maximum line length is now 100.

> +{
> +	switch (type) {
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_input:
> +			return 0444;
> +			break;
> +		case hwmon_fan_target:
> +			return 0644;
> +			break;
> +		case hwmon_fan_min:
> +			return 0444;
> +			break;
> +		default:
> +			break;
> +		}
> +		break;
> +	case hwmon_pwm:
> +		switch (attr) {
> +		case hwmon_pwm_input:
> +			return 0200;
> +			break;
> +		case hwmon_pwm_mode:
> +			return 0644;
> +			break;
> +		default:
> +			break;
> +		}
> +		break;
> +
> +	default:
> +		break;
> +	}
> +	return 0;
> +}
> +
> +static int write_func(struct device *dev, enum hwmon_sensor_types type,
> +		      u32 attr, int channel, long val)
> +{
> +	struct hwmon_data *data = dev_get_drvdata(dev);
> +	struct driver *cdev = data->dev;
> +	struct hwmon_fan_data *fan_data;
> +	int retval = 0;
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_target:
> +			fan_data = data->channel_data[channel];
> +			if (fan_data->mode != 1)
> +				return -EINVAL;
> +
> +			retval = usb_autopm_get_interface(cdev->interface);
> +			if (retval)
> +				goto exit;
> +
> +			if (down_trylock(&cdev->limit_sem)) {
> +				retval = -EAGAIN;
> +				goto cleanup_interface;
> +			}
> +
> +			retval = set_fan_target_rpm(cdev, fan_data, val);
> +			if (retval)
> +				goto cleanup;
> +
> +			goto exit;

All those "goto exit;" are useless and inconsistent.

> +		default:
> +			return -EINVAL;
> +		}
> +		goto exit;
> +	case hwmon_pwm:
> +		switch (attr) {
> +		case hwmon_pwm_input:
> +			fan_data = data->channel_data[channel];
> +			if (fan_data->mode != 1)
> +				return -EINVAL;
> +
> +			retval = usb_autopm_get_interface(cdev->interface);
> +			if (retval)
> +				goto exit;
> +
> +			if (down_trylock(&cdev->limit_sem)) {
> +				retval = -EAGAIN;
> +				goto cleanup_interface;
> +			}
> +
> +			retval = set_fan_target_pwm(cdev, fan_data, val);
> +			if (retval)
> +				return retval;
> +
> +			goto cleanup;

goto are expected to be used for error cleanup, not for normal function exits.

> +		case hwmon_pwm_enable:
> +			fan_data = data->channel_data[channel];
> +
> +			retval = usb_autopm_get_interface(cdev->interface);
> +			if (retval)
> +				goto exit;
> +
> +			if (down_trylock(&cdev->limit_sem)) {
> +				retval = -EAGAIN;
> +				goto cleanup_interface;
> +			}
> +			fan_data->mode = val;
> +
> +			switch (val) {
> +			case 0:
> +				set_fan_rpm_curve(cdev, fan_data,
> +						  default_curve);
> +				break;
> +			case 1:
> +				if (fan_data->fan_target != 0) {
> +					retval = set_fan_target_rpm(
> +						cdev, fan_data,
> +						fan_data->fan_target);
> +					if (retval)
> +						goto cleanup;
> +				} else if (fan_data->fan_pwm_target != 0) {
> +					retval = set_fan_target_pwm(
> +						cdev, fan_data,
> +						fan_data->fan_pwm_target);
> +					if (retval)
> +						goto cleanup;
> +				}
> +				break;
> +			case 2:
> +				set_fan_rpm_curve(cdev, fan_data, quiet_curve);
> +				break;
> +			case 3:
> +				set_fan_rpm_curve(cdev, fan_data,
> +						  balanced_curve);
> +				break;
> +			case 4:
> +				set_fan_rpm_curve(cdev, fan_data,
> +						  extreme_curve);
> +				break;
> +			}

"pwm_enable" is not supposed to be used for parameter settings as done here.
It is supposd to be used to enable/disable automatic fan control.

> +			goto cleanup;
> +		default:
> +			return -EINVAL;
> +		}
> +		goto exit;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +cleanup:
> +	up(&cdev->limit_sem);
> +cleanup_interface:
> +	usb_autopm_put_interface(cdev->interface);
> +exit:
> +	return retval;
> +}
> +
> +int read_func(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +	      int channel, long *val)
> +{
> +	struct hwmon_data *data = dev_get_drvdata(dev);
> +	struct driver *cdev = data->dev;
> +	struct hwmon_fan_data *fan_data;
> +	int retval = 0;
> +
> +	if (channel >= data->channel_count)
> +		return -EAGAIN;
> +
> +	switch (type) {
> +	case hwmon_fan:
> +		switch (attr) {
> +		case hwmon_fan_input:
> +			fan_data = data->channel_data[channel];
> +
> +			retval = usb_autopm_get_interface(cdev->interface);
> +			if (retval)
> +				goto exit;
> +
> +			if (down_trylock(&cdev->limit_sem)) {
> +				retval = -EAGAIN;
> +				goto cleanup_interface;
> +			}
> +
> +			retval = get_fan_current_rpm(cdev, fan_data, val);
> +			if (retval)
> +				goto cleanup;
> +
> +			goto cleanup;
> +		case hwmon_fan_target:
> +			fan_data = data->channel_data[channel];
> +			if (fan_data->mode != 1) {
> +				*val = 0;
> +				goto exit;
> +			}
> +
> +			get_fan_target_rpm(fan_data, val);
> +			goto exit;
> +		case hwmon_fan_min:
> +			*val = 200;
> +			goto exit;
> +
> +		default:
> +			return -EINVAL;
> +		}
> +		goto exit;
> +
> +	case hwmon_pwm:
> +		switch (attr) {
> +		case hwmon_pwm_mode:
> +			fan_data = data->channel_data[channel];
> +			*val = fan_data->mode;
> +			goto exit;
> +		default:
> +			return -EINVAL;
> +		}
> +		goto exit;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +cleanup:
> +	up(&cdev->limit_sem);
> +cleanup_interface:
> +	usb_autopm_put_interface(cdev->interface);
> +exit:
> +	return retval;
> +}
> +
> +#define fan_config (HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_MIN)
> +#define pwm_config (HWMON_PWM_INPUT | HWMON_PWM_ENABLE)
> +
> +static const struct hwmon_ops asetek_6_ops = {
> +	.is_visible = is_visible_func,
> +	.read = read_func,
> +	.write = write_func,
> +};
> +
> +bool does_fan_exist(struct driver *cdev, int channel)
> +{
> +	int retval;
> +	int wrote;
> +	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> +	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> +
> +	char *send_buf = cdev->bulk_out_buffer;
> +	char *recv_buf = cdev->bulk_in_buffer;
> +
> +	send_buf[0] = 0x43;
> +	send_buf[1] = channel;
> +	send_buf[2] = (600 >> 8);
> +	send_buf[3] = 600;
> +
> +	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> +	if (retval != 0)
> +		return false;
> +
> +	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
> +	if (retval != 0)
> +		return false;
> +
> +	if (!check_succes(send_buf[0], recv_buf))
> +		return false;
> +	return true;
> +}
> +
> +int get_fan_count(struct driver *dev)
> +{
> +	int fan;
> +
> +	for (fan = 0; does_fan_exist(dev, fan); fan += 1)
> +		;
> +	return fan;
> +}
> +
> +void hwmon_init(struct driver *dev)
> +{
> +	int fan_id;
> +	struct device *hwmon_dev;
> +	struct hwmon_fan_data *fan;
> +	struct hwmon_data *data = devm_kzalloc(
> +		&dev->udev->dev, sizeof(struct hwmon_data), GFP_KERNEL);
> +	struct hwmon_chip_info *hwmon_info = devm_kzalloc(
> +		&dev->udev->dev, sizeof(struct hwmon_chip_info), GFP_KERNEL);
> +	//Allocate the info table
> +	struct hwmon_channel_info **aio_info =
> +		devm_kzalloc(&dev->udev->dev,
> +			     sizeof(struct hwmon_channel_info *) * 2,
> +			     GFP_KERNEL); //2 for each channel info.
> +
> +	//Allocate the fan and PWM configuration
> +	u32 *fans_config = devm_kzalloc(&dev->udev->dev,
> +					sizeof(u32) * (data->channel_count + 1),
> +					GFP_KERNEL);
> +	u32 *pwms_config = devm_kzalloc(&dev->udev->dev,
> +					sizeof(u32) * (data->channel_count + 1),
> +					GFP_KERNEL);
> +
> +	data->channel_count = get_fan_count(dev); //amount of fans
> +	data->channel_data =
> +		devm_kzalloc(&dev->udev->dev,
> +			     sizeof(char *) * data->channel_count, GFP_KERNEL);
> +
> +	//For each fan create a data channel a fan config entry and a pwm config entry

Please no C++ comments

> +	for (fan_id = 0; fan_id <= data->channel_count; fan_id++) {
> +		fan = devm_kzalloc(&dev->udev->dev,
> +				   sizeof(struct hwmon_fan_data), GFP_KERNEL);
> +		fan->fan_channel = fan_id;
> +		fan->mode = 2;
> +		data->channel_data[fan_id] = fan;
> +		fans_config[fan_id] = fan_config;
> +		pwms_config[fan_id] = pwm_config;
> +	}
> +	fans_config[data->channel_count] = 0;
> +	pwms_config[data->channel_count] = 0;
> +
> +	aio_info[0] = devm_kzalloc(
> +		&dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
> +	aio_info[0]->type = hwmon_fan;
> +	aio_info[0]->config = fans_config;
> +
> +	aio_info[1] = devm_kzalloc(
> +		&dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
> +	aio_info[1]->type = hwmon_pwm;
> +	aio_info[1]->config = pwms_config;
> +
> +	hwmon_info->ops = &asetek_6_ops;
> +	hwmon_info->info = (const struct hwmon_channel_info **)aio_info;
> +
> +	data->dev = dev;
> +	hwmon_dev = devm_hwmon_device_register_with_info(
> +		&dev->udev->dev, "driver_fan", data, hwmon_info, NULL);
> +	dev_info("[*] Setup hwmon\n");

Does this even compile ?

> +}
> +
> +void hwmon_deinit(struct driver *dev)
> +{
> +	hwmon_device_unregister(&dev->udev->dev);

Defeats the purpose of devm_hwmon_device_register_with_info().

> +	dev_info("[*] HWMON DISCONNECT\n");
> +}
> +
> +/*
> + * Devices that work with this driver.
> + * More devices should work, however none have been tested.
> + */
> +static const struct usb_device_id astk_table[] = {
> +	{ USB_DEVICE(0x1b1c, 0x0c15) },
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(usb, astk_table);
> +
> +int init_device(struct usb_device *udev)
> +{
> +	int retval;
> +
> +	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x00, 0x40,
> +				 0xffff, 0x0000, 0, 0, 0);
> +	//this always returns error

Needs explanation why it is needed.

> +	if (retval != 0)
> +		;
> +
> +	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
> +				 0x0002, 0x0000, 0, 0, 0);
> +	if (retval != 0)
> +		return retval;
> +
> +	return 0;

	return retval;

> +}
> +
> +int deinit_device(struct usb_device *udev)
> +{
> +	int retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
> +				     0x0004, 0x0000, 0, 0, 0);
> +	if (retval != 0)
> +		return retval;
> +
> +	return 0;

> +}
> +
> +static void astk_delete(struct kref *kref)
> +{
> +	struct driver *dev = container_of(kref, struct driver, kref);
> +
> +	usb_put_intf(dev->interface);
> +	usb_put_dev(dev->udev);
> +	kfree(dev->bulk_in_buffer);
> +	kfree(dev->bulk_out_buffer);
> +	kfree(dev);
> +}
> +
> +static int astk_probe(struct usb_interface *interface,
> +		      const struct usb_device_id *id)
> +{
> +	struct driver *dev;
> +	int retval = 0;

Unnecessary initialization.

> +	struct usb_endpoint_descriptor *bulk_in, *bulk_out;
> +
> +	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +	if (!dev) {
> +		retval = -ENOMEM;
> +		goto exit;
> +	}
> +
> +	retval = usb_find_common_endpoints(interface->cur_altsetting, &bulk_in,
> +					   &bulk_out, NULL, NULL);
> +	if (retval != 0)
> +		goto exit;
> +
> +	dev->udev = usb_get_dev(interface_to_usbdev(interface));
> +	dev->interface = usb_get_intf(interface);
> +
> +	/* set up the endpoint information */
> +	/* use only the first bulk-in and bulk-out endpoints */
> +	dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
> +	dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
> +	dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
> +	dev->bulk_out_size = usb_endpoint_maxp(bulk_out);
> +	dev->bulk_out_buffer = kmalloc(dev->bulk_out_size, GFP_KERNEL);
> +	dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
> +
> +	retval = init_device(dev->udev);
> +	if (retval) {
> +		dev_err(&interface->dev, "Failled initialising this device.\n");
> +		goto exit;
> +	}
> +
> +	hwmon_init(dev);
> +
> +	usb_set_intfdata(interface, dev);
> +	kref_init(&dev->kref);
> +	mutex_init(&dev->io_mutex);
> +	sema_init(&dev->limit_sem, 8);
> +exit:
> +	return retval;
> +}
> +
> +static void astk_disconnect(struct usb_interface *interface)
> +{
> +	struct driver *dev = usb_get_intfdata(interface);
> +
> +	hwmon_deinit(dev);
> +	usb_set_intfdata(interface, NULL);
> +	kref_put(&dev->kref, astk_delete);
> +	deinit_device(dev->udev);
> +}
> +static int astk_resume(struct usb_interface *intf)
> +{
> +	return 0;
> +}
> +
> +static int astk_suspend(struct usb_interface *intf, pm_message_t message)
> +{
> +	return 0;
> +}

What is the point of those functions if they don't do anything ?

> +
> +static struct usb_driver astk_driver = {
> +	.name = "Asetek gen6 driver",
> +	.id_table = astk_table,
> +	.probe = astk_probe,
> +	.disconnect = astk_disconnect,
> +	.resume = astk_resume,
> +	.suspend = astk_suspend,
> +	.supports_autosuspend = 1,
> +};
> +
> +static int __init astk_init(void)
> +{
> +	int ret = usb_register(&astk_driver);
> +
> +	return ret;

	return usb_register(...);

> +}
> +
> +static void __exit astk_exit(void)
> +{
> +	usb_deregister(&astk_driver);
> +}
> +
> +module_init(astk_init);
> +module_exit(astk_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Jaap Aarts <jaap.aarts1@gmail.com>");
> +MODULE_DESCRIPTION("Asetek gen6 driver");
> -- 
> 2.27.0
>
Greg KH July 15, 2020, 8:34 a.m. UTC | #3
On Tue, Jul 14, 2020 at 08:07:40PM -0700, Guenter Roeck wrote:
> On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > Adds fan/pwm support for H1000i platinum.
> > Custom temp/fan curves are not supported, however
> > the presets found in the proprietary drivers are avaiable.
> > 
> > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> 
> +Marius Zachmann for input.
> 
> Questions:
> - Does this really have to be a different driver or can it be merged into
>   the corsair-cpro driver ?
> - What about HID vs. USB driver ?
> - is the kref complexity really needed ?

At first glance, I can't tell what the kref is trying to keep track of.

So I would say no, it's not. :)

thanks,

greg k-h
jaap aarts July 15, 2020, 11:18 a.m. UTC | #4
On Wed, 15 Jul 2020 at 05:07, Guenter Roeck <linux@roeck-us.net> wrote:
>
> On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > Adds fan/pwm support for H1000i platinum.
> > Custom temp/fan curves are not supported, however
> > the presets found in the proprietary drivers are avaiable.
> >
> > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
>
> +Marius Zachmann for input.
>
> Questions:
> - Does this really have to be a different driver or can it be merged into
>   the corsair-cpro driver ?
I cannot find this driver at the moment, the only corsair driver I can find
is the HID driver. As far as I know all asetek gen 6 products use the same
interface. Out of curiosity I contacted asetek to confirm, but other userland
drivers have used the same code for all products of previous generations.
> - What about HID vs. USB driver ?
This is not really a HID. I asked in the kernel newbies mailing list and
I was told HWMON is probably the right place. Most of the code is
related to HWMON so this seems to be the right place to me as well.
> - is the kref complexity really needed ?
That is leftover from the usb_skeleton driver, I assumed it was
necessary. Apparently not so I will remove this
>
> > ---
> >  drivers/hwmon/Kconfig       |   6 +
> >  drivers/hwmon/Makefile      |   1 +
> >  drivers/hwmon/asetek_gen6.c | 801 ++++++++++++++++++++++++++++++++++++
> >  3 files changed, 808 insertions(+)
> >  create mode 100644 drivers/hwmon/asetek_gen6.c
> >
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index 288ae9f63588..521a9e0c88ca 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -378,6 +378,12 @@ config SENSORS_ARM_SCPI
> >         and power sensors available on ARM Ltd's SCP based platforms. The
> >         actual number and type of sensors exported depend on the platform.
> >
> > +config SENSORS_ASETEK_GEN6
> > +     tristate "Asetek generation 6 driver"
> > +     help
> > +       If you say yes here you get support for asetek generation 6 boards
> > +       found on most AIO liquid coolers with an asetek pump.
> > +
> >  config SENSORS_ASB100
> >       tristate "Asus ASB100 Bach"
> >       depends on X86 && I2C
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 3e32c21f5efe..9683d2aae2b2 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -20,6 +20,7 @@ obj-$(CONFIG_SENSORS_W83793)        += w83793.o
> >  obj-$(CONFIG_SENSORS_W83795) += w83795.o
> >  obj-$(CONFIG_SENSORS_W83781D)        += w83781d.o
> >  obj-$(CONFIG_SENSORS_W83791D)        += w83791d.o
> > +obj-$(CONFIG_SENSORS_ASETEK_GEN6)    += asetek_gen6.o
> >
> >  obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o
> >  obj-$(CONFIG_SENSORS_ABITUGURU)      += abituguru.o
> > diff --git a/drivers/hwmon/asetek_gen6.c b/drivers/hwmon/asetek_gen6.c
> > new file mode 100644
> > index 000000000000..4aea9ae0b944
> > --- /dev/null
> > +++ b/drivers/hwmon/asetek_gen6.c
> > @@ -0,0 +1,801 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * A hwmon driver for most asetek gen 6 all-in-one liquid coolers.
> > + * Copyright (c) Jaap Aarts 2020
> > + *
> > + * Protocol reverse engineered by audiohacked
> > + * https://github.com/audiohacked/OpendriverLink
> > + */
> > +
> > +/*
> > + * Supports following chips:
>
> This isn't really a chip, it is a liquid cooler.
fixed
>
> > + * h100i platinum
> > + *
> > + * Other products should work with this driver but no testing has been done.
> > + *
> > + * Note: platinum is the codename name for pro within driver, so h100i platinum = h1ooi pro
>
> h1ooi ? o's instead of 0 ?
fixed
>
> > + *
> > + * Note: fan curve control has not been implemented
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/kernel.h>
> > +#include <linux/usb.h>
> > +#include <linux/slab.h>
> > +#include <linux/mutex.h>
> > +#include <linux/errno.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
>
> Alphabetic order, please. Also, it doesn't look like hwmon-sysfs.h is used.
fixed
>
> > +
> > +struct driver {
>
> this is a terribly generic name for a local structure.
DIdn't really think about this, named it asetek_gen6_device.
Also named all variables of this type to adev, previously
udev/dev (inconsistent)
>
> > +     struct usb_device *udev;
> > +
> > +     char *bulk_out_buffer;
> > +     char *bulk_in_buffer;
> > +     size_t bulk_out_size;
> > +     size_t bulk_in_size;
> > +     char bulk_in_endpointAddr;
> > +     char bulk_out_endpointAddr;
> > +
> > +     struct usb_interface *interface; /* the interface for this device */
> > +     struct mutex io_mutex; /* synchronize I/O with disconnect */
>
> Not used anywhere.
Yup, I thought I caught all of those
>
> > +     struct semaphore
> > +             limit_sem; /* limiting the number of writes in progress */
>
> I don't see the need for a semaphore in this driver.
After each write there is always a read, I dont want another hwmon
call to write before the first call reads. This might cause incorrect
error messages.
>
> > +
> > +     struct kref kref;
> > +};
> > +
> > +struct curve_point {
> > +     uint8_t temp;
> > +     uint8_t pwm;
> > +};
> > +
> > +struct hwmon_fan_data {
> > +     char fan_channel;
> > +     long fan_target;
> > +     unsigned char fan_pwm_target;
> > +     long mode;
> > +     struct curve_point curve[7];
> > +};
> > +
> > +struct hwmon_data {
> > +     struct driver *dev;
> > +     int channel_count;
> > +     void **channel_data;
> > +};
> > +
> > +struct curve_point quiet_curve[] = {
> > +     {
> > +             .temp = 0x1F,
> > +             .pwm = 0x15,
> > +     },
> > +     {
> > +             .temp = 0x21,
> > +             .pwm = 0x1E,
> > +     },
> > +     {
> > +             .temp = 0x24,
> > +             .pwm = 0x25,
> > +     },
> > +     {
> > +             .temp = 0x27,
> > +             .pwm = 0x2D,
> > +     },
> > +     {
> > +             .temp = 0x29,
> > +             .pwm = 0x38,
> > +     },
> > +     {
> > +             .temp = 0x2C,
> > +             .pwm = 0x4A,
> > +     },
> > +     {
> > +             .temp = 0x2F,
> > +             .pwm = 0x64,
> > +     },
> > +};
> > +
> > +struct curve_point balanced_curve[] = {
> > +     {
> > +             .temp = 0x1C,
> > +             .pwm = 0x15,
> > +     },
> > +     {
> > +             .temp = 0x1E,
> > +             .pwm = 0x1B,
> > +     },
> > +     {
> > +             .temp = 0x20,
> > +             .pwm = 0x23,
> > +     },
> > +     {
> > +             .temp = 0x22,
> > +             .pwm = 0x28,
> > +     },
> > +     {
> > +             .temp = 0x24,
> > +             .pwm = 0x32,
> > +     },
> > +     {
> > +             .temp = 0x27,
> > +             .pwm = 0x48,
> > +     },
> > +     {
> > +             .temp = 0x29,
> > +             .pwm = 0x64,
> > +     },
> > +};
> > +
> > +struct curve_point extreme_curve[] = {
> > +     {
> > +             .temp = 0x19,
> > +             .pwm = 0x28,
> > +     },
> > +     {
> > +             .temp = 0x1B,
> > +             .pwm = 0x2E,
> > +     },
> > +     {
> > +             .temp = 0x1D,
> > +             .pwm = 0x37,
> > +     },
> > +     {
> > +             .temp = 0x1E,
> > +             .pwm = 0x41,
> > +     },
> > +     {
> > +             .temp = 0x1F,
> > +             .pwm = 0x4C,
> > +     },
> > +     {
> > +             .temp = 0x20,
> > +             .pwm = 0x56,
> > +     },
> > +     {
> > +             .temp = 0x21,
> > +             .pwm = 0x64,
> > +     },
> > +};
>
> We don't get into the business of deciding fan curves in the kernel.
> The driver should implement sets of {pwm_auto_pointN_temp,
> pwm_auto_pointN_pwm} instead and leave it up to userspace to set
> actual values.
I have thought about that, I am willing to remove the non-default fan curves.
The default must be there since the asetek board doesn't have a way to reset
to the default profile AFAIK. I don't want the user to lock themselves into
manual control until the reboot.
>
> > +
> > +#define default_curve quiet_curve
> > +
> > +static const char SUCCESS[2] = { 0x12, 0x34 };
> > +
> > +#define SUCCES_LENGTH 3
> > +
> > +static bool check_succes(char command, char ret[SUCCES_LENGTH])
> > +{
> > +     char success[SUCCES_LENGTH] = { command };
> > +
> > +     strncpy(&success[1], SUCCESS, SUCCES_LENGTH - 1);
> > +     return strncmp(ret, success, SUCCES_LENGTH - 1) == 0;
> > +}
>
> This seems terribly expensive and complicated. I have not spent time trying
> to analyze what it actually does, but it seems highly unlikely that such a
> complex evaluation is needed.
The success return values are always 0x{command}1234 this just puts them
together by creating an array with the first value set to {command} and copies
0x1234 into the other 0 bytes. This then compares that to the actual result.
>
> > +
> > +int set_fan_rpm_curve(struct driver *cdev, struct hwmon_fan_data *fan_data,
> > +                   struct curve_point point[7])
> > +{
> > +     int retval;
> > +     int wrote;
> > +     int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> > +     int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> > +     char *send_buf = cdev->bulk_out_buffer;
> > +     char *recv_buf = cdev->bulk_in_buffer;
> > +
> > +     memcpy(fan_data->curve, point, sizeof(fan_data->curve));
> > +
> > +     send_buf[0] = 0x40;
> > +     send_buf[1] = fan_data->fan_channel;
> > +     send_buf[2] = point[0].temp;
> > +     send_buf[3] = point[1].temp;
> > +     send_buf[4] = point[2].temp;
> > +     send_buf[5] = point[3].temp;
> > +     send_buf[6] = point[4].temp;
> > +     send_buf[7] = point[5].temp;
> > +     send_buf[8] = point[6].temp;
> > +     send_buf[9] = point[0].pwm;
> > +     send_buf[10] = point[1].pwm;
> > +     send_buf[11] = point[2].pwm;
> > +     send_buf[12] = point[3].pwm;
> > +     send_buf[13] = point[4].pwm;
> > +     send_buf[14] = point[5].pwm;
> > +     send_buf[15] = point[6].pwm;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 16, &wrote, 100);
> > +     if (retval != 0)
>
>         if (retval)
Is it okay to be more verbose? I prefer to not depend on behaviour like this.
> > +             return retval;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 4, &wrote, 100);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     if (!check_succes(send_buf[0], recv_buf))
> > +             dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
> > +                      recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
>
> No error return ? Then drop the check.
Added a -EINVAL error
>
> > +     return 0;
> > +}
> > +
> > +int set_fan_target_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> > +                    long val)
> > +{
> > +     int retval;
> > +     int wrote;
> > +     int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> > +     int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> > +
> > +     char *send_buf = cdev->bulk_out_buffer;
> > +     char *recv_buf = cdev->bulk_in_buffer;
> > +
> > +     fan_data->fan_target = val;
> > +     fan_data->fan_pwm_target = 0;
> > +
> > +     send_buf[0] = 0x43;
>
> Please use defines instead of magic values.
Is an enum ok as well?
>
> > +     send_buf[1] = fan_data->fan_channel;
> > +     send_buf[2] = (fan_data->fan_target >> 8);
> > +     send_buf[3] = fan_data->fan_target;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
>
> 100 seconds timeout is a bit unreal.
Fixed
>
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     //no error
>
> Useless comment.
Removed
>
> > +     if (!check_succes(send_buf[0], recv_buf))
> > +             dev_info("[*] Failled setting fan rpm %d,%d,%d/%d\n",
> > +                      recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> > +     return 0;
> > +}
> > +
> > +void get_fan_target_rpm(struct hwmon_fan_data *fan_data, long *val)
> > +{
> > +     *val = fan_data->fan_target;
> > +}
>
> This function does not add any value. Caller can access fan_data->fan_target
> directly.
Yes, I initially had this because I liked everything being a function
but at some
point it did become a bit silly/
>
> > +
> > +int get_fan_current_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> > +                     long *val)
> > +{
> > +     int retval;
> > +     int wrote;
> > +     int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> > +     int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> > +
> > +     char *send_buf = cdev->bulk_out_buffer;
> > +     char *recv_buf = cdev->bulk_in_buffer;
> > +
> > +     send_buf[0] = 0x41;
> > +     send_buf[1] = fan_data->fan_channel;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 2, &wrote, 100);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     if (!check_succes(0x41, recv_buf) ||
> > +         recv_buf[3] != fan_data->fan_channel)
> > +             dev_info("[*] Failled retrieving fan rmp %d,%d,%d/%d\n",
>
> Failed, rpm. But that message is really pointless (as is the check),
> since the result is ignored and whatever is in the buffer is returned
> to the caller anyway.
Should I remove the messages? I added error returns to all these checks.
>
> > +                      recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> > +
> > +     *val = (((uint8_t)recv_buf[4]) << 8) + (uint8_t)recv_buf[5];
>
> Declaring send_buf and recv_buf as char pointer just to typecast recv_buf
> is not really useful and just makes the code more complex.
I had problems with shifting and formatting, I changed everything over to
unsigned char, everything works with that type.
>
> > +     return 0;
> > +}
> > +
> > +int set_fan_target_pwm(struct driver *cdev, struct hwmon_fan_data *fan_data,
> > +                    long val)
> > +{
> > +     int retval;
> > +     int wrote;
> > +     int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> > +     int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> > +
> > +     unsigned char *send_buf = cdev->bulk_out_buffer;
> > +     unsigned char *recv_buf = cdev->bulk_in_buffer;
>
> Now we have char, uint8_t, and unsigned char. Please be consistent.
switched everything over to unsigned char
>
> > +
> > +     fan_data->fan_pwm_target = val;
> > +     fan_data->fan_target = 0;
> > +
> > +     send_buf[0] = 0x42;
> > +     send_buf[1] = fan_data->fan_channel;
> > +     send_buf[3] = fan_data->fan_pwm_target;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     //no error
> > +     if (!check_succes(send_buf[0], recv_buf))
> > +             dev_info("[*] Failled setting fan pwm %d,%d,%d/%d\n",
> > +                      recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
> > +     return 0;
> > +}
> > +
> > +umode_t is_visible_func(const void *d, enum hwmon_sensor_types type, u32 attr,
>
> _func is quite pointless in function names.
You're right, fixed this
>
> > +                     int channel)
>
> Maximum line length is now 100.
wdym? My formatter automatically picks up formatting settings from somewhere.
I checked `ColumnLimit: 80` is still in .clan-format.
>
> > +{
> > +     switch (type) {
> > +     case hwmon_fan:
> > +             switch (attr) {
> > +             case hwmon_fan_input:
> > +                     return 0444;
> > +                     break;
> > +             case hwmon_fan_target:
> > +                     return 0644;
> > +                     break;
> > +             case hwmon_fan_min:
> > +                     return 0444;
> > +                     break;
> > +             default:
> > +                     break;
> > +             }
> > +             break;
> > +     case hwmon_pwm:
> > +             switch (attr) {
> > +             case hwmon_pwm_input:
> > +                     return 0200;
> > +                     break;
> > +             case hwmon_pwm_mode:
> > +                     return 0644;
> > +                     break;
> > +             default:
> > +                     break;
> > +             }
> > +             break;
> > +
> > +     default:
> > +             break;
> > +     }
> > +     return 0;
> > +}
> > +
> > +static int write_func(struct device *dev, enum hwmon_sensor_types type,
> > +                   u32 attr, int channel, long val)
> > +{
> > +     struct hwmon_data *data = dev_get_drvdata(dev);
> > +     struct driver *cdev = data->dev;
> > +     struct hwmon_fan_data *fan_data;
> > +     int retval = 0;
> > +
> > +     switch (type) {
> > +     case hwmon_fan:
> > +             switch (attr) {
> > +             case hwmon_fan_target:
> > +                     fan_data = data->channel_data[channel];
> > +                     if (fan_data->mode != 1)
> > +                             return -EINVAL;
> > +
> > +                     retval = usb_autopm_get_interface(cdev->interface);
> > +                     if (retval)
> > +                             goto exit;
> > +
> > +                     if (down_trylock(&cdev->limit_sem)) {
> > +                             retval = -EAGAIN;
> > +                             goto cleanup_interface;
> > +                     }
> > +
> > +                     retval = set_fan_target_rpm(cdev, fan_data, val);
> > +                     if (retval)
> > +                             goto cleanup;
> > +
> > +                     goto exit;
>
> All those "goto exit;" are useless and inconsistent.
I think I made them more consistent, I am not used to using
goto for cleanup.
>
> > +             default:
> > +                     return -EINVAL;
> > +             }
> > +             goto exit;
> > +     case hwmon_pwm:
> > +             switch (attr) {
> > +             case hwmon_pwm_input:
> > +                     fan_data = data->channel_data[channel];
> > +                     if (fan_data->mode != 1)
> > +                             return -EINVAL;
> > +
> > +                     retval = usb_autopm_get_interface(cdev->interface);
> > +                     if (retval)
> > +                             goto exit;
> > +
> > +                     if (down_trylock(&cdev->limit_sem)) {
> > +                             retval = -EAGAIN;
> > +                             goto cleanup_interface;
> > +                     }
> > +
> > +                     retval = set_fan_target_pwm(cdev, fan_data, val);
> > +                     if (retval)
> > +                             return retval;
> > +
> > +                     goto cleanup;
>
> goto are expected to be used for error cleanup, not for normal function exits.
Agreed
>
> > +             case hwmon_pwm_enable:
> > +                     fan_data = data->channel_data[channel];
> > +
> > +                     retval = usb_autopm_get_interface(cdev->interface);
> > +                     if (retval)
> > +                             goto exit;
> > +
> > +                     if (down_trylock(&cdev->limit_sem)) {
> > +                             retval = -EAGAIN;
> > +                             goto cleanup_interface;
> > +                     }
> > +                     fan_data->mode = val;
> > +
> > +                     switch (val) {
> > +                     case 0:
> > +                             set_fan_rpm_curve(cdev, fan_data,
> > +                                               default_curve);
> > +                             break;
> > +                     case 1:
> > +                             if (fan_data->fan_target != 0) {
> > +                                     retval = set_fan_target_rpm(
> > +                                             cdev, fan_data,
> > +                                             fan_data->fan_target);
> > +                                     if (retval)
> > +                                             goto cleanup;
> > +                             } else if (fan_data->fan_pwm_target != 0) {
> > +                                     retval = set_fan_target_pwm(
> > +                                             cdev, fan_data,
> > +                                             fan_data->fan_pwm_target);
> > +                                     if (retval)
> > +                                             goto cleanup;
> > +                             }
> > +                             break;
> > +                     case 2:
> > +                             set_fan_rpm_curve(cdev, fan_data, quiet_curve);
> > +                             break;
> > +                     case 3:
> > +                             set_fan_rpm_curve(cdev, fan_data,
> > +                                               balanced_curve);
> > +                             break;
> > +                     case 4:
> > +                             set_fan_rpm_curve(cdev, fan_data,
> > +                                               extreme_curve);
> > +                             break;
> > +                     }
>
> "pwm_enable" is not supposed to be used for parameter settings as done here.
> It is supposd to be used to enable/disable automatic fan control.
https://www.kernel.org/doc/html/v5.8-rc1/hwmon/sysfs-interface.html
Says 2+ are supposed to be used for automatic control, this is also why
I added the fan curves in this driver.
>
> > +                     goto cleanup;
> > +             default:
> > +                     return -EINVAL;
> > +             }
> > +             goto exit;
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +cleanup:
> > +     up(&cdev->limit_sem);
> > +cleanup_interface:
> > +     usb_autopm_put_interface(cdev->interface);
> > +exit:
> > +     return retval;
> > +}
> > +
> > +int read_func(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> > +           int channel, long *val)
> > +{
> > +     struct hwmon_data *data = dev_get_drvdata(dev);
> > +     struct driver *cdev = data->dev;
> > +     struct hwmon_fan_data *fan_data;
> > +     int retval = 0;
> > +
> > +     if (channel >= data->channel_count)
> > +             return -EAGAIN;
> > +
> > +     switch (type) {
> > +     case hwmon_fan:
> > +             switch (attr) {
> > +             case hwmon_fan_input:
> > +                     fan_data = data->channel_data[channel];
> > +
> > +                     retval = usb_autopm_get_interface(cdev->interface);
> > +                     if (retval)
> > +                             goto exit;
> > +
> > +                     if (down_trylock(&cdev->limit_sem)) {
> > +                             retval = -EAGAIN;
> > +                             goto cleanup_interface;
> > +                     }
> > +
> > +                     retval = get_fan_current_rpm(cdev, fan_data, val);
> > +                     if (retval)
> > +                             goto cleanup;
> > +
> > +                     goto cleanup;
> > +             case hwmon_fan_target:
> > +                     fan_data = data->channel_data[channel];
> > +                     if (fan_data->mode != 1) {
> > +                             *val = 0;
> > +                             goto exit;
> > +                     }
> > +
> > +                     get_fan_target_rpm(fan_data, val);
> > +                     goto exit;
> > +             case hwmon_fan_min:
> > +                     *val = 200;
> > +                     goto exit;
> > +
> > +             default:
> > +                     return -EINVAL;
> > +             }
> > +             goto exit;
> > +
> > +     case hwmon_pwm:
> > +             switch (attr) {
> > +             case hwmon_pwm_mode:
> > +                     fan_data = data->channel_data[channel];
> > +                     *val = fan_data->mode;
> > +                     goto exit;
> > +             default:
> > +                     return -EINVAL;
> > +             }
> > +             goto exit;
> > +
> > +     default:
> > +             return -EINVAL;
> > +     }
> > +
> > +cleanup:
> > +     up(&cdev->limit_sem);
> > +cleanup_interface:
> > +     usb_autopm_put_interface(cdev->interface);
> > +exit:
> > +     return retval;
> > +}
> > +
> > +#define fan_config (HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_MIN)
> > +#define pwm_config (HWMON_PWM_INPUT | HWMON_PWM_ENABLE)
> > +
> > +static const struct hwmon_ops asetek_6_ops = {
> > +     .is_visible = is_visible_func,
> > +     .read = read_func,
> > +     .write = write_func,
> > +};
> > +
> > +bool does_fan_exist(struct driver *cdev, int channel)
> > +{
> > +     int retval;
> > +     int wrote;
> > +     int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
> > +     int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
> > +
> > +     char *send_buf = cdev->bulk_out_buffer;
> > +     char *recv_buf = cdev->bulk_in_buffer;
> > +
> > +     send_buf[0] = 0x43;
> > +     send_buf[1] = channel;
> > +     send_buf[2] = (600 >> 8);
> > +     send_buf[3] = 600;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
> > +     if (retval != 0)
> > +             return false;
> > +
> > +     retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
> > +     if (retval != 0)
> > +             return false;
> > +
> > +     if (!check_succes(send_buf[0], recv_buf))
> > +             return false;
> > +     return true;
> > +}
> > +
> > +int get_fan_count(struct driver *dev)
> > +{
> > +     int fan;
> > +
> > +     for (fan = 0; does_fan_exist(dev, fan); fan += 1)
> > +             ;
> > +     return fan;
> > +}
> > +
> > +void hwmon_init(struct driver *dev)
> > +{
> > +     int fan_id;
> > +     struct device *hwmon_dev;
> > +     struct hwmon_fan_data *fan;
> > +     struct hwmon_data *data = devm_kzalloc(
> > +             &dev->udev->dev, sizeof(struct hwmon_data), GFP_KERNEL);
> > +     struct hwmon_chip_info *hwmon_info = devm_kzalloc(
> > +             &dev->udev->dev, sizeof(struct hwmon_chip_info), GFP_KERNEL);
> > +     //Allocate the info table
> > +     struct hwmon_channel_info **aio_info =
> > +             devm_kzalloc(&dev->udev->dev,
> > +                          sizeof(struct hwmon_channel_info *) * 2,
> > +                          GFP_KERNEL); //2 for each channel info.
> > +
> > +     //Allocate the fan and PWM configuration
> > +     u32 *fans_config = devm_kzalloc(&dev->udev->dev,
> > +                                     sizeof(u32) * (data->channel_count + 1),
> > +                                     GFP_KERNEL);
> > +     u32 *pwms_config = devm_kzalloc(&dev->udev->dev,
> > +                                     sizeof(u32) * (data->channel_count + 1),
> > +                                     GFP_KERNEL);
> > +
> > +     data->channel_count = get_fan_count(dev); //amount of fans
> > +     data->channel_data =
> > +             devm_kzalloc(&dev->udev->dev,
> > +                          sizeof(char *) * data->channel_count, GFP_KERNEL);
> > +
> > +     //For each fan create a data channel a fan config entry and a pwm config entry
>
> Please no C++ comments
what are C++ comments?
I felt like this code was really confusing to look at, and wanted to
bring some clarity.
>
> > +     for (fan_id = 0; fan_id <= data->channel_count; fan_id++) {
> > +             fan = devm_kzalloc(&dev->udev->dev,
> > +                                sizeof(struct hwmon_fan_data), GFP_KERNEL);
> > +             fan->fan_channel = fan_id;
> > +             fan->mode = 2;
> > +             data->channel_data[fan_id] = fan;
> > +             fans_config[fan_id] = fan_config;
> > +             pwms_config[fan_id] = pwm_config;
> > +     }
> > +     fans_config[data->channel_count] = 0;
> > +     pwms_config[data->channel_count] = 0;
> > +
> > +     aio_info[0] = devm_kzalloc(
> > +             &dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
> > +     aio_info[0]->type = hwmon_fan;
> > +     aio_info[0]->config = fans_config;
> > +
> > +     aio_info[1] = devm_kzalloc(
> > +             &dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
> > +     aio_info[1]->type = hwmon_pwm;
> > +     aio_info[1]->config = pwms_config;
> > +
> > +     hwmon_info->ops = &asetek_6_ops;
> > +     hwmon_info->info = (const struct hwmon_channel_info **)aio_info;
> > +
> > +     data->dev = dev;
> > +     hwmon_dev = devm_hwmon_device_register_with_info(
> > +             &dev->udev->dev, "driver_fan", data, hwmon_info, NULL);
> > +     dev_info("[*] Setup hwmon\n");
>
> Does this even compile ?
I wish I could say no it does not, but it does. I would love to have a
vendor/device -id->configuration map, but I am honestly not sure
how I would implement that in C at the moment, let alone if that
would be allowed in the kernel.
>
> > +}
> > +
> > +void hwmon_deinit(struct driver *dev)
> > +{
> > +     hwmon_device_unregister(&dev->udev->dev);
>
> Defeats the purpose of devm_hwmon_device_register_with_info().
I thought the managed part was talking about devm_kzalloc.
If not, I'm sorry I misunderstood the documentation.
Removed this.
>
> > +     dev_info("[*] HWMON DISCONNECT\n");
> > +}
> > +
> > +/*
> > + * Devices that work with this driver.
> > + * More devices should work, however none have been tested.
> > + */
> > +static const struct usb_device_id astk_table[] = {
> > +     { USB_DEVICE(0x1b1c, 0x0c15) },
> > +     {},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(usb, astk_table);
> > +
> > +int init_device(struct usb_device *udev)
> > +{
> > +     int retval;
> > +
> > +     retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x00, 0x40,
> > +                              0xffff, 0x0000, 0, 0, 0);
> > +     //this always returns error
>
> Needs explanation why it is needed.
added.
>
> > +     if (retval != 0)
> > +             ;
> > +
> > +     retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
> > +                              0x0002, 0x0000, 0, 0, 0);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     return 0;
>
>         return retval;
fixed
>
> > +}
> > +
> > +int deinit_device(struct usb_device *udev)
> > +{
> > +     int retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
> > +                                  0x0004, 0x0000, 0, 0, 0);
> > +     if (retval != 0)
> > +             return retval;
> > +
> > +     return 0;
>
> > +}
> > +
> > +static void astk_delete(struct kref *kref)
> > +{
> > +     struct driver *dev = container_of(kref, struct driver, kref);
> > +
> > +     usb_put_intf(dev->interface);
> > +     usb_put_dev(dev->udev);
> > +     kfree(dev->bulk_in_buffer);
> > +     kfree(dev->bulk_out_buffer);
> > +     kfree(dev);
> > +}
> > +
> > +static int astk_probe(struct usb_interface *interface,
> > +                   const struct usb_device_id *id)
> > +{
> > +     struct driver *dev;
> > +     int retval = 0;
>
> Unnecessary initialization.
fixed
>
> > +     struct usb_endpoint_descriptor *bulk_in, *bulk_out;
> > +
> > +     dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> > +     if (!dev) {
> > +             retval = -ENOMEM;
> > +             goto exit;
> > +     }
> > +
> > +     retval = usb_find_common_endpoints(interface->cur_altsetting, &bulk_in,
> > +                                        &bulk_out, NULL, NULL);
> > +     if (retval != 0)
> > +             goto exit;
> > +
> > +     dev->udev = usb_get_dev(interface_to_usbdev(interface));
> > +     dev->interface = usb_get_intf(interface);
> > +
> > +     /* set up the endpoint information */
> > +     /* use only the first bulk-in and bulk-out endpoints */
> > +     dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
> > +     dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
> > +     dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
> > +     dev->bulk_out_size = usb_endpoint_maxp(bulk_out);
> > +     dev->bulk_out_buffer = kmalloc(dev->bulk_out_size, GFP_KERNEL);
> > +     dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
> > +
> > +     retval = init_device(dev->udev);
> > +     if (retval) {
> > +             dev_err(&interface->dev, "Failled initialising this device.\n");
> > +             goto exit;
> > +     }
> > +
> > +     hwmon_init(dev);
> > +
> > +     usb_set_intfdata(interface, dev);
> > +     kref_init(&dev->kref);
> > +     mutex_init(&dev->io_mutex);
> > +     sema_init(&dev->limit_sem, 8);
> > +exit:
> > +     return retval;
> > +}
> > +
> > +static void astk_disconnect(struct usb_interface *interface)
> > +{
> > +     struct driver *dev = usb_get_intfdata(interface);
> > +
> > +     hwmon_deinit(dev);
> > +     usb_set_intfdata(interface, NULL);
> > +     kref_put(&dev->kref, astk_delete);
> > +     deinit_device(dev->udev);
> > +}
> > +static int astk_resume(struct usb_interface *intf)
> > +{
> > +     return 0;
> > +}
> > +
> > +static int astk_suspend(struct usb_interface *intf, pm_message_t message)
> > +{
> > +     return 0;
> > +}
>
> What is the point of those functions if they don't do anything ?
They are needed for USB power management. If I do not provide
these functions the USB subsystem cannot use autosuspend.
>
> > +
> > +static struct usb_driver astk_driver = {
> > +     .name = "Asetek gen6 driver",
> > +     .id_table = astk_table,
> > +     .probe = astk_probe,
> > +     .disconnect = astk_disconnect,
> > +     .resume = astk_resume,
> > +     .suspend = astk_suspend,
> > +     .supports_autosuspend = 1,
> > +};
> > +
> > +static int __init astk_init(void)
> > +{
> > +     int ret = usb_register(&astk_driver);
> > +
> > +     return ret;
>
>         return usb_register(...);
fixed
>
> > +}
> > +
> > +static void __exit astk_exit(void)
> > +{
> > +     usb_deregister(&astk_driver);
> > +}
> > +
> > +module_init(astk_init);
> > +module_exit(astk_exit);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_AUTHOR("Jaap Aarts <jaap.aarts1@gmail.com>");
> > +MODULE_DESCRIPTION("Asetek gen6 driver");
> > --
> > 2.27.0
> >

After fixing most of these comments should I send in a new patchset?
Or should I reply with my new patch to this thread.
Marius Zachmann July 15, 2020, 12:17 p.m. UTC | #5
On 15.07.20 at 05:07:40 CEST, Guenter Roeck wrote
> On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > Adds fan/pwm support for H1000i platinum.
> > Custom temp/fan curves are not supported, however
> > the presets found in the proprietary drivers are avaiable.
> > 
> > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> 
> +Marius Zachmann for input.
> 
> Questions:
> - Does this really have to be a different driver or can it be merged into
>   the corsair-cpro driver ?
>

From what I can see the protocol has quite a few differences.
A merged driver would need to implement functions like e.g. set_pwm for
each device seperately. Also error handling and buffer sizes would be
seperate for each device.
If there were more usb/hid drivers in hwmon it maybe could make sense
to have an additional abstraction layer, but for now I do not see
anything which could be gained by this.

Greetings
Marius
Guenter Roeck July 15, 2020, 2:20 p.m. UTC | #6
On Wed, Jul 15, 2020 at 02:17:37PM +0200, Marius Zachmann wrote:
> On 15.07.20 at 05:07:40 CEST, Guenter Roeck wrote
> > On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > > Adds fan/pwm support for H1000i platinum.
> > > Custom temp/fan curves are not supported, however
> > > the presets found in the proprietary drivers are avaiable.
> > > 
> > > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> > 
> > +Marius Zachmann for input.
> > 
> > Questions:
> > - Does this really have to be a different driver or can it be merged into
> >   the corsair-cpro driver ?
> >
> 
> From what I can see the protocol has quite a few differences.
> A merged driver would need to implement functions like e.g. set_pwm for
> each device seperately. Also error handling and buffer sizes would be
> seperate for each device.
> If there were more usb/hid drivers in hwmon it maybe could make sense
> to have an additional abstraction layer, but for now I do not see
> anything which could be gained by this.
> 
Ok.

Thanks,
Guenter
Guenter Roeck July 15, 2020, 2:47 p.m. UTC | #7
On Wed, Jul 15, 2020 at 01:18:58PM +0200, jaap aarts wrote:
> On Wed, 15 Jul 2020 at 05:07, Guenter Roeck <linux@roeck-us.net> wrote:
> >
> > On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > > Adds fan/pwm support for H1000i platinum.
> > > Custom temp/fan curves are not supported, however
> > > the presets found in the proprietary drivers are avaiable.
> > >
> > > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> >
> > +Marius Zachmann for input.
> >
> > Questions:
> > - Does this really have to be a different driver or can it be merged into
> >   the corsair-cpro driver ?
> I cannot find this driver at the moment, the only corsair driver I can find
> is the HID driver. As far as I know all asetek gen 6 products use the same
> interface. Out of curiosity I contacted asetek to confirm, but other userland
> drivers have used the same code for all products of previous generations.
> > - What about HID vs. USB driver ?
> This is not really a HID. I asked in the kernel newbies mailing list and
> I was told HWMON is probably the right place. Most of the code is
> related to HWMON so this seems to be the right place to me as well.

Question is if this identifies itself as HID device. If it does,
it would either have to be blacklisted in the HID core, or it would
have to be implemented as hid driver. The latter would be preferred,
since otherwise a userspace application accessing it directly would
no longer work. Either case, the driver can and should still reside
in hwmon; that was not the question.

Thanks,
Guenter
jaap aarts July 15, 2020, 3:03 p.m. UTC | #8
On Wed, 15 Jul 2020 at 16:47, Guenter Roeck <linux@roeck-us.net> wrote:
>
> On Wed, Jul 15, 2020 at 01:18:58PM +0200, jaap aarts wrote:
> > On Wed, 15 Jul 2020 at 05:07, Guenter Roeck <linux@roeck-us.net> wrote:
> > >
> > > On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > > > Adds fan/pwm support for H1000i platinum.
> > > > Custom temp/fan curves are not supported, however
> > > > the presets found in the proprietary drivers are avaiable.
> > > >
> > > > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> > >
> > > +Marius Zachmann for input.
> > >
> > > Questions:
> > > - Does this really have to be a different driver or can it be merged into
> > >   the corsair-cpro driver ?
> > I cannot find this driver at the moment, the only corsair driver I can find
> > is the HID driver. As far as I know all asetek gen 6 products use the same
> > interface. Out of curiosity I contacted asetek to confirm, but other userland
> > drivers have used the same code for all products of previous generations.
> > > - What about HID vs. USB driver ?
> > This is not really a HID. I asked in the kernel newbies mailing list and
> > I was told HWMON is probably the right place. Most of the code is
> > related to HWMON so this seems to be the right place to me as well.
>
> Question is if this identifies itself as HID device. If it does,
> it would either have to be blacklisted in the HID core, or it would
> have to be implemented as hid driver. The latter would be preferred,
> since otherwise a userspace application accessing it directly would
> no longer work. Either case, the driver can and should still reside
> in hwmon; that was not the question.
Thanks for the clarification, lsusb lists bInterfaceClass as 255 or
"Vendor Specific Class", so not as a HID.
>
> Thanks,
> Guenter
Guenter Roeck July 15, 2020, 3:10 p.m. UTC | #9
On Wed, Jul 15, 2020 at 05:03:52PM +0200, jaap aarts wrote:
> On Wed, 15 Jul 2020 at 16:47, Guenter Roeck <linux@roeck-us.net> wrote:
> >
> > On Wed, Jul 15, 2020 at 01:18:58PM +0200, jaap aarts wrote:
> > > On Wed, 15 Jul 2020 at 05:07, Guenter Roeck <linux@roeck-us.net> wrote:
> > > >
> > > > On Tue, Jul 14, 2020 at 12:03:38PM +0200, jaap aarts wrote:
> > > > > Adds fan/pwm support for H1000i platinum.
> > > > > Custom temp/fan curves are not supported, however
> > > > > the presets found in the proprietary drivers are avaiable.
> > > > >
> > > > > Signed-off-by: Jaap Aarts <jaap.aarts1@gmail.com>
> > > >
> > > > +Marius Zachmann for input.
> > > >
> > > > Questions:
> > > > - Does this really have to be a different driver or can it be merged into
> > > >   the corsair-cpro driver ?
> > > I cannot find this driver at the moment, the only corsair driver I can find
> > > is the HID driver. As far as I know all asetek gen 6 products use the same
> > > interface. Out of curiosity I contacted asetek to confirm, but other userland
> > > drivers have used the same code for all products of previous generations.
> > > > - What about HID vs. USB driver ?
> > > This is not really a HID. I asked in the kernel newbies mailing list and
> > > I was told HWMON is probably the right place. Most of the code is
> > > related to HWMON so this seems to be the right place to me as well.
> >
> > Question is if this identifies itself as HID device. If it does,
> > it would either have to be blacklisted in the HID core, or it would
> > have to be implemented as hid driver. The latter would be preferred,
> > since otherwise a userspace application accessing it directly would
> > no longer work. Either case, the driver can and should still reside
> > in hwmon; that was not the question.
> Thanks for the clarification, lsusb lists bInterfaceClass as 255 or
> "Vendor Specific Class", so not as a HID.

Ok.

Thanks,
Guenter
jaap aarts July 17, 2020, 12:20 p.m. UTC | #10
I found out that since asetek generation 5 the resellers put their own
boards in the product,
so the new patch I sent in has fixed all the things mentioned as
fixed/changed and is renamed.
diff mbox series

Patch

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 288ae9f63588..521a9e0c88ca 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -378,6 +378,12 @@  config SENSORS_ARM_SCPI
 	  and power sensors available on ARM Ltd's SCP based platforms. The
 	  actual number and type of sensors exported depend on the platform.
 
+config SENSORS_ASETEK_GEN6
+	tristate "Asetek generation 6 driver"
+	help
+	  If you say yes here you get support for asetek generation 6 boards
+	  found on most AIO liquid coolers with an asetek pump.
+
 config SENSORS_ASB100
 	tristate "Asus ASB100 Bach"
 	depends on X86 && I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 3e32c21f5efe..9683d2aae2b2 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -20,6 +20,7 @@  obj-$(CONFIG_SENSORS_W83793)	+= w83793.o
 obj-$(CONFIG_SENSORS_W83795)	+= w83795.o
 obj-$(CONFIG_SENSORS_W83781D)	+= w83781d.o
 obj-$(CONFIG_SENSORS_W83791D)	+= w83791d.o
+obj-$(CONFIG_SENSORS_ASETEK_GEN6)	+= asetek_gen6.o
 
 obj-$(CONFIG_SENSORS_AB8500)	+= abx500.o ab8500.o
 obj-$(CONFIG_SENSORS_ABITUGURU)	+= abituguru.o
diff --git a/drivers/hwmon/asetek_gen6.c b/drivers/hwmon/asetek_gen6.c
new file mode 100644
index 000000000000..4aea9ae0b944
--- /dev/null
+++ b/drivers/hwmon/asetek_gen6.c
@@ -0,0 +1,801 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A hwmon driver for most asetek gen 6 all-in-one liquid coolers.
+ * Copyright (c) Jaap Aarts 2020
+ * 
+ * Protocol reverse engineered by audiohacked
+ * https://github.com/audiohacked/OpendriverLink
+ */
+
+/*
+ * Supports following chips:
+ * h100i platinum
+ * 
+ * Other products should work with this driver but no testing has been done.
+ * 
+ * Note: platinum is the codename name for pro within driver, so h100i platinum = h1ooi pro
+ * 
+ * Note: fan curve control has not been implemented
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/errno.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+struct driver {
+	struct usb_device *udev;
+
+	char *bulk_out_buffer;
+	char *bulk_in_buffer;
+	size_t bulk_out_size;
+	size_t bulk_in_size;
+	char bulk_in_endpointAddr;
+	char bulk_out_endpointAddr;
+
+	struct usb_interface *interface; /* the interface for this device */
+	struct mutex io_mutex; /* synchronize I/O with disconnect */
+	struct semaphore
+		limit_sem; /* limiting the number of writes in progress */
+
+	struct kref kref;
+};
+
+struct curve_point {
+	uint8_t temp;
+	uint8_t pwm;
+};
+
+struct hwmon_fan_data {
+	char fan_channel;
+	long fan_target;
+	unsigned char fan_pwm_target;
+	long mode;
+	struct curve_point curve[7];
+};
+
+struct hwmon_data {
+	struct driver *dev;
+	int channel_count;
+	void **channel_data;
+};
+
+struct curve_point quiet_curve[] = {
+	{
+		.temp = 0x1F,
+		.pwm = 0x15,
+	},
+	{
+		.temp = 0x21,
+		.pwm = 0x1E,
+	},
+	{
+		.temp = 0x24,
+		.pwm = 0x25,
+	},
+	{
+		.temp = 0x27,
+		.pwm = 0x2D,
+	},
+	{
+		.temp = 0x29,
+		.pwm = 0x38,
+	},
+	{
+		.temp = 0x2C,
+		.pwm = 0x4A,
+	},
+	{
+		.temp = 0x2F,
+		.pwm = 0x64,
+	},
+};
+
+struct curve_point balanced_curve[] = {
+	{
+		.temp = 0x1C,
+		.pwm = 0x15,
+	},
+	{
+		.temp = 0x1E,
+		.pwm = 0x1B,
+	},
+	{
+		.temp = 0x20,
+		.pwm = 0x23,
+	},
+	{
+		.temp = 0x22,
+		.pwm = 0x28,
+	},
+	{
+		.temp = 0x24,
+		.pwm = 0x32,
+	},
+	{
+		.temp = 0x27,
+		.pwm = 0x48,
+	},
+	{
+		.temp = 0x29,
+		.pwm = 0x64,
+	},
+};
+
+struct curve_point extreme_curve[] = {
+	{
+		.temp = 0x19,
+		.pwm = 0x28,
+	},
+	{
+		.temp = 0x1B,
+		.pwm = 0x2E,
+	},
+	{
+		.temp = 0x1D,
+		.pwm = 0x37,
+	},
+	{
+		.temp = 0x1E,
+		.pwm = 0x41,
+	},
+	{
+		.temp = 0x1F,
+		.pwm = 0x4C,
+	},
+	{
+		.temp = 0x20,
+		.pwm = 0x56,
+	},
+	{
+		.temp = 0x21,
+		.pwm = 0x64,
+	},
+};
+
+#define default_curve quiet_curve
+
+static const char SUCCESS[2] = { 0x12, 0x34 };
+
+#define SUCCES_LENGTH 3
+
+static bool check_succes(char command, char ret[SUCCES_LENGTH])
+{
+	char success[SUCCES_LENGTH] = { command };
+
+	strncpy(&success[1], SUCCESS, SUCCES_LENGTH - 1);
+	return strncmp(ret, success, SUCCES_LENGTH - 1) == 0;
+}
+
+int set_fan_rpm_curve(struct driver *cdev, struct hwmon_fan_data *fan_data,
+		      struct curve_point point[7])
+{
+	int retval;
+	int wrote;
+	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
+	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
+	char *send_buf = cdev->bulk_out_buffer;
+	char *recv_buf = cdev->bulk_in_buffer;
+
+	memcpy(fan_data->curve, point, sizeof(fan_data->curve));
+
+	send_buf[0] = 0x40;
+	send_buf[1] = fan_data->fan_channel;
+	send_buf[2] = point[0].temp;
+	send_buf[3] = point[1].temp;
+	send_buf[4] = point[2].temp;
+	send_buf[5] = point[3].temp;
+	send_buf[6] = point[4].temp;
+	send_buf[7] = point[5].temp;
+	send_buf[8] = point[6].temp;
+	send_buf[9] = point[0].pwm;
+	send_buf[10] = point[1].pwm;
+	send_buf[11] = point[2].pwm;
+	send_buf[12] = point[3].pwm;
+	send_buf[13] = point[4].pwm;
+	send_buf[14] = point[5].pwm;
+	send_buf[15] = point[6].pwm;
+
+	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 16, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 4, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	if (!check_succes(send_buf[0], recv_buf))
+		dev_info("[*] Failled setting fan curve %d,%d,%d/%d\n",
+			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
+	return 0;
+}
+
+int set_fan_target_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
+		       long val)
+{
+	int retval;
+	int wrote;
+	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
+	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
+
+	char *send_buf = cdev->bulk_out_buffer;
+	char *recv_buf = cdev->bulk_in_buffer;
+
+	fan_data->fan_target = val;
+	fan_data->fan_pwm_target = 0;
+
+	send_buf[0] = 0x43;
+	send_buf[1] = fan_data->fan_channel;
+	send_buf[2] = (fan_data->fan_target >> 8);
+	send_buf[3] = fan_data->fan_target;
+
+	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
+	if (retval != 0)
+		return retval;
+
+	//no error
+	if (!check_succes(send_buf[0], recv_buf))
+		dev_info("[*] Failled setting fan rpm %d,%d,%d/%d\n",
+			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
+	return 0;
+}
+
+void get_fan_target_rpm(struct hwmon_fan_data *fan_data, long *val)
+{
+	*val = fan_data->fan_target;
+}
+
+int get_fan_current_rpm(struct driver *cdev, struct hwmon_fan_data *fan_data,
+			long *val)
+{
+	int retval;
+	int wrote;
+	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
+	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
+
+	char *send_buf = cdev->bulk_out_buffer;
+	char *recv_buf = cdev->bulk_in_buffer;
+
+	send_buf[0] = 0x41;
+	send_buf[1] = fan_data->fan_channel;
+
+	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 2, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	if (!check_succes(0x41, recv_buf) ||
+	    recv_buf[3] != fan_data->fan_channel)
+		dev_info("[*] Failled retrieving fan rmp %d,%d,%d/%d\n",
+			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
+
+	*val = (((uint8_t)recv_buf[4]) << 8) + (uint8_t)recv_buf[5];
+	return 0;
+}
+
+int set_fan_target_pwm(struct driver *cdev, struct hwmon_fan_data *fan_data,
+		       long val)
+{
+	int retval;
+	int wrote;
+	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
+	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
+
+	unsigned char *send_buf = cdev->bulk_out_buffer;
+	unsigned char *recv_buf = cdev->bulk_in_buffer;
+
+	fan_data->fan_pwm_target = val;
+	fan_data->fan_target = 0;
+
+	send_buf[0] = 0x42;
+	send_buf[1] = fan_data->fan_channel;
+	send_buf[3] = fan_data->fan_pwm_target;
+
+	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
+	if (retval != 0)
+		return retval;
+
+	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
+	if (retval != 0)
+		return retval;
+
+	//no error
+	if (!check_succes(send_buf[0], recv_buf))
+		dev_info("[*] Failled setting fan pwm %d,%d,%d/%d\n",
+			 recv_buf[0], recv_buf[1], recv_buf[2], recv_buf[3]);
+	return 0;
+}
+
+umode_t is_visible_func(const void *d, enum hwmon_sensor_types type, u32 attr,
+			int channel)
+{
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			return 0444;
+			break;
+		case hwmon_fan_target:
+			return 0644;
+			break;
+		case hwmon_fan_min:
+			return 0444;
+			break;
+		default:
+			break;
+		}
+		break;
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_input:
+			return 0200;
+			break;
+		case hwmon_pwm_mode:
+			return 0644;
+			break;
+		default:
+			break;
+		}
+		break;
+
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int write_func(struct device *dev, enum hwmon_sensor_types type,
+		      u32 attr, int channel, long val)
+{
+	struct hwmon_data *data = dev_get_drvdata(dev);
+	struct driver *cdev = data->dev;
+	struct hwmon_fan_data *fan_data;
+	int retval = 0;
+
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_target:
+			fan_data = data->channel_data[channel];
+			if (fan_data->mode != 1)
+				return -EINVAL;
+
+			retval = usb_autopm_get_interface(cdev->interface);
+			if (retval)
+				goto exit;
+
+			if (down_trylock(&cdev->limit_sem)) {
+				retval = -EAGAIN;
+				goto cleanup_interface;
+			}
+
+			retval = set_fan_target_rpm(cdev, fan_data, val);
+			if (retval)
+				goto cleanup;
+
+			goto exit;
+		default:
+			return -EINVAL;
+		}
+		goto exit;
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_input:
+			fan_data = data->channel_data[channel];
+			if (fan_data->mode != 1)
+				return -EINVAL;
+
+			retval = usb_autopm_get_interface(cdev->interface);
+			if (retval)
+				goto exit;
+
+			if (down_trylock(&cdev->limit_sem)) {
+				retval = -EAGAIN;
+				goto cleanup_interface;
+			}
+
+			retval = set_fan_target_pwm(cdev, fan_data, val);
+			if (retval)
+				return retval;
+
+			goto cleanup;
+		case hwmon_pwm_enable:
+			fan_data = data->channel_data[channel];
+
+			retval = usb_autopm_get_interface(cdev->interface);
+			if (retval)
+				goto exit;
+
+			if (down_trylock(&cdev->limit_sem)) {
+				retval = -EAGAIN;
+				goto cleanup_interface;
+			}
+			fan_data->mode = val;
+
+			switch (val) {
+			case 0:
+				set_fan_rpm_curve(cdev, fan_data,
+						  default_curve);
+				break;
+			case 1:
+				if (fan_data->fan_target != 0) {
+					retval = set_fan_target_rpm(
+						cdev, fan_data,
+						fan_data->fan_target);
+					if (retval)
+						goto cleanup;
+				} else if (fan_data->fan_pwm_target != 0) {
+					retval = set_fan_target_pwm(
+						cdev, fan_data,
+						fan_data->fan_pwm_target);
+					if (retval)
+						goto cleanup;
+				}
+				break;
+			case 2:
+				set_fan_rpm_curve(cdev, fan_data, quiet_curve);
+				break;
+			case 3:
+				set_fan_rpm_curve(cdev, fan_data,
+						  balanced_curve);
+				break;
+			case 4:
+				set_fan_rpm_curve(cdev, fan_data,
+						  extreme_curve);
+				break;
+			}
+			goto cleanup;
+		default:
+			return -EINVAL;
+		}
+		goto exit;
+	default:
+		return -EINVAL;
+	}
+
+cleanup:
+	up(&cdev->limit_sem);
+cleanup_interface:
+	usb_autopm_put_interface(cdev->interface);
+exit:
+	return retval;
+}
+
+int read_func(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+	      int channel, long *val)
+{
+	struct hwmon_data *data = dev_get_drvdata(dev);
+	struct driver *cdev = data->dev;
+	struct hwmon_fan_data *fan_data;
+	int retval = 0;
+
+	if (channel >= data->channel_count)
+		return -EAGAIN;
+
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			fan_data = data->channel_data[channel];
+
+			retval = usb_autopm_get_interface(cdev->interface);
+			if (retval)
+				goto exit;
+
+			if (down_trylock(&cdev->limit_sem)) {
+				retval = -EAGAIN;
+				goto cleanup_interface;
+			}
+
+			retval = get_fan_current_rpm(cdev, fan_data, val);
+			if (retval)
+				goto cleanup;
+
+			goto cleanup;
+		case hwmon_fan_target:
+			fan_data = data->channel_data[channel];
+			if (fan_data->mode != 1) {
+				*val = 0;
+				goto exit;
+			}
+
+			get_fan_target_rpm(fan_data, val);
+			goto exit;
+		case hwmon_fan_min:
+			*val = 200;
+			goto exit;
+
+		default:
+			return -EINVAL;
+		}
+		goto exit;
+
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_mode:
+			fan_data = data->channel_data[channel];
+			*val = fan_data->mode;
+			goto exit;
+		default:
+			return -EINVAL;
+		}
+		goto exit;
+
+	default:
+		return -EINVAL;
+	}
+
+cleanup:
+	up(&cdev->limit_sem);
+cleanup_interface:
+	usb_autopm_put_interface(cdev->interface);
+exit:
+	return retval;
+}
+
+#define fan_config (HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_MIN)
+#define pwm_config (HWMON_PWM_INPUT | HWMON_PWM_ENABLE)
+
+static const struct hwmon_ops asetek_6_ops = {
+	.is_visible = is_visible_func,
+	.read = read_func,
+	.write = write_func,
+};
+
+bool does_fan_exist(struct driver *cdev, int channel)
+{
+	int retval;
+	int wrote;
+	int sndpipe = usb_sndbulkpipe(cdev->udev, cdev->bulk_out_endpointAddr);
+	int rcvpipe = usb_rcvbulkpipe(cdev->udev, cdev->bulk_in_endpointAddr);
+
+	char *send_buf = cdev->bulk_out_buffer;
+	char *recv_buf = cdev->bulk_in_buffer;
+
+	send_buf[0] = 0x43;
+	send_buf[1] = channel;
+	send_buf[2] = (600 >> 8);
+	send_buf[3] = 600;
+
+	retval = usb_bulk_msg(cdev->udev, sndpipe, send_buf, 4, &wrote, 100);
+	if (retval != 0)
+		return false;
+
+	retval = usb_bulk_msg(cdev->udev, rcvpipe, recv_buf, 6, &wrote, 100000);
+	if (retval != 0)
+		return false;
+
+	if (!check_succes(send_buf[0], recv_buf))
+		return false;
+	return true;
+}
+
+int get_fan_count(struct driver *dev)
+{
+	int fan;
+
+	for (fan = 0; does_fan_exist(dev, fan); fan += 1)
+		;
+	return fan;
+}
+
+void hwmon_init(struct driver *dev)
+{
+	int fan_id;
+	struct device *hwmon_dev;
+	struct hwmon_fan_data *fan;
+	struct hwmon_data *data = devm_kzalloc(
+		&dev->udev->dev, sizeof(struct hwmon_data), GFP_KERNEL);
+	struct hwmon_chip_info *hwmon_info = devm_kzalloc(
+		&dev->udev->dev, sizeof(struct hwmon_chip_info), GFP_KERNEL);
+	//Allocate the info table
+	struct hwmon_channel_info **aio_info =
+		devm_kzalloc(&dev->udev->dev,
+			     sizeof(struct hwmon_channel_info *) * 2,
+			     GFP_KERNEL); //2 for each channel info.
+
+	//Allocate the fan and PWM configuration
+	u32 *fans_config = devm_kzalloc(&dev->udev->dev,
+					sizeof(u32) * (data->channel_count + 1),
+					GFP_KERNEL);
+	u32 *pwms_config = devm_kzalloc(&dev->udev->dev,
+					sizeof(u32) * (data->channel_count + 1),
+					GFP_KERNEL);
+
+	data->channel_count = get_fan_count(dev); //amount of fans
+	data->channel_data =
+		devm_kzalloc(&dev->udev->dev,
+			     sizeof(char *) * data->channel_count, GFP_KERNEL);
+
+	//For each fan create a data channel a fan config entry and a pwm config entry
+	for (fan_id = 0; fan_id <= data->channel_count; fan_id++) {
+		fan = devm_kzalloc(&dev->udev->dev,
+				   sizeof(struct hwmon_fan_data), GFP_KERNEL);
+		fan->fan_channel = fan_id;
+		fan->mode = 2;
+		data->channel_data[fan_id] = fan;
+		fans_config[fan_id] = fan_config;
+		pwms_config[fan_id] = pwm_config;
+	}
+	fans_config[data->channel_count] = 0;
+	pwms_config[data->channel_count] = 0;
+
+	aio_info[0] = devm_kzalloc(
+		&dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
+	aio_info[0]->type = hwmon_fan;
+	aio_info[0]->config = fans_config;
+
+	aio_info[1] = devm_kzalloc(
+		&dev->udev->dev, sizeof(struct hwmon_channel_info), GFP_KERNEL);
+	aio_info[1]->type = hwmon_pwm;
+	aio_info[1]->config = pwms_config;
+
+	hwmon_info->ops = &asetek_6_ops;
+	hwmon_info->info = (const struct hwmon_channel_info **)aio_info;
+
+	data->dev = dev;
+	hwmon_dev = devm_hwmon_device_register_with_info(
+		&dev->udev->dev, "driver_fan", data, hwmon_info, NULL);
+	dev_info("[*] Setup hwmon\n");
+}
+
+void hwmon_deinit(struct driver *dev)
+{
+	hwmon_device_unregister(&dev->udev->dev);
+	dev_info("[*] HWMON DISCONNECT\n");
+}
+
+/*
+ * Devices that work with this driver.
+ * More devices should work, however none have been tested.
+ */
+static const struct usb_device_id astk_table[] = {
+	{ USB_DEVICE(0x1b1c, 0x0c15) },
+	{},
+};
+
+MODULE_DEVICE_TABLE(usb, astk_table);
+
+int init_device(struct usb_device *udev)
+{
+	int retval;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x00, 0x40,
+				 0xffff, 0x0000, 0, 0, 0);
+	//this always returns error
+	if (retval != 0)
+		;
+
+	retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
+				 0x0002, 0x0000, 0, 0, 0);
+	if (retval != 0)
+		return retval;
+
+	return 0;
+}
+
+int deinit_device(struct usb_device *udev)
+{
+	int retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x02, 0x40,
+				     0x0004, 0x0000, 0, 0, 0);
+	if (retval != 0)
+		return retval;
+
+	return 0;
+}
+
+static void astk_delete(struct kref *kref)
+{
+	struct driver *dev = container_of(kref, struct driver, kref);
+
+	usb_put_intf(dev->interface);
+	usb_put_dev(dev->udev);
+	kfree(dev->bulk_in_buffer);
+	kfree(dev->bulk_out_buffer);
+	kfree(dev);
+}
+
+static int astk_probe(struct usb_interface *interface,
+		      const struct usb_device_id *id)
+{
+	struct driver *dev;
+	int retval = 0;
+	struct usb_endpoint_descriptor *bulk_in, *bulk_out;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		retval = -ENOMEM;
+		goto exit;
+	}
+
+	retval = usb_find_common_endpoints(interface->cur_altsetting, &bulk_in,
+					   &bulk_out, NULL, NULL);
+	if (retval != 0)
+		goto exit;
+
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+	dev->interface = usb_get_intf(interface);
+
+	/* set up the endpoint information */
+	/* use only the first bulk-in and bulk-out endpoints */
+	dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
+	dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
+	dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
+	dev->bulk_out_size = usb_endpoint_maxp(bulk_out);
+	dev->bulk_out_buffer = kmalloc(dev->bulk_out_size, GFP_KERNEL);
+	dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
+
+	retval = init_device(dev->udev);
+	if (retval) {
+		dev_err(&interface->dev, "Failled initialising this device.\n");
+		goto exit;
+	}
+
+	hwmon_init(dev);
+
+	usb_set_intfdata(interface, dev);
+	kref_init(&dev->kref);
+	mutex_init(&dev->io_mutex);
+	sema_init(&dev->limit_sem, 8);
+exit:
+	return retval;
+}
+
+static void astk_disconnect(struct usb_interface *interface)
+{
+	struct driver *dev = usb_get_intfdata(interface);
+
+	hwmon_deinit(dev);
+	usb_set_intfdata(interface, NULL);
+	kref_put(&dev->kref, astk_delete);
+	deinit_device(dev->udev);
+}
+static int astk_resume(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static int astk_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	return 0;
+}
+
+static struct usb_driver astk_driver = {
+	.name = "Asetek gen6 driver",
+	.id_table = astk_table,
+	.probe = astk_probe,
+	.disconnect = astk_disconnect,
+	.resume = astk_resume,
+	.suspend = astk_suspend,
+	.supports_autosuspend = 1,
+};
+
+static int __init astk_init(void)
+{
+	int ret = usb_register(&astk_driver);
+
+	return ret;
+}
+
+static void __exit astk_exit(void)
+{
+	usb_deregister(&astk_driver);
+}
+
+module_init(astk_init);
+module_exit(astk_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jaap Aarts <jaap.aarts1@gmail.com>");
+MODULE_DESCRIPTION("Asetek gen6 driver");