Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
9a6f62b5
Commit
9a6f62b5
authored
Dec 13, 2022
by
Jiri Kosina
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'for-6.2/mcp2221' into for-linus
- iio support for the MCP2221 HID driver (Matt Ranostay)
parents
3daac75d
3d74c9ec
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
290 additions
and
26 deletions
+290
-26
drivers/hid/Kconfig
drivers/hid/Kconfig
+2
-1
drivers/hid/hid-mcp2221.c
drivers/hid/hid-mcp2221.c
+288
-25
No files found.
drivers/hid/Kconfig
View file @
9a6f62b5
...
@@ -1252,7 +1252,8 @@ config HID_ALPS
...
@@ -1252,7 +1252,8 @@ config HID_ALPS
config HID_MCP2221
config HID_MCP2221
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
depends on USB_HID && I2C
depends on USB_HID && I2C
depends on GPIOLIB
imply GPIOLIB
imply IIO
help
help
Provides I2C and SMBUS host adapter functionality over USB-HID
Provides I2C and SMBUS host adapter functionality over USB-HID
through MCP2221 device.
through MCP2221 device.
...
...
drivers/hid/hid-mcp2221.c
View file @
9a6f62b5
...
@@ -10,12 +10,14 @@
...
@@ -10,12 +10,14 @@
#include <linux/module.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/mutex.h>
#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/delay.h>
#include <linux/hid.h>
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/i2c.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/driver.h>
#include <linux/iio/iio.h>
#include "hid-ids.h"
#include "hid-ids.h"
/* Commands codes in a raw output report */
/* Commands codes in a raw output report */
...
@@ -30,6 +32,9 @@ enum {
...
@@ -30,6 +32,9 @@ enum {
MCP2221_I2C_CANCEL
=
0x10
,
MCP2221_I2C_CANCEL
=
0x10
,
MCP2221_GPIO_SET
=
0x50
,
MCP2221_GPIO_SET
=
0x50
,
MCP2221_GPIO_GET
=
0x51
,
MCP2221_GPIO_GET
=
0x51
,
MCP2221_SET_SRAM_SETTINGS
=
0x60
,
MCP2221_GET_SRAM_SETTINGS
=
0x61
,
MCP2221_READ_FLASH_DATA
=
0xb0
,
};
};
/* Response codes in a raw input report */
/* Response codes in a raw input report */
...
@@ -89,6 +94,7 @@ struct mcp2221 {
...
@@ -89,6 +94,7 @@ struct mcp2221 {
struct
i2c_adapter
adapter
;
struct
i2c_adapter
adapter
;
struct
mutex
lock
;
struct
mutex
lock
;
struct
completion
wait_in_report
;
struct
completion
wait_in_report
;
struct
delayed_work
init_work
;
u8
*
rxbuf
;
u8
*
rxbuf
;
u8
txbuf
[
64
];
u8
txbuf
[
64
];
int
rxbuf_idx
;
int
rxbuf_idx
;
...
@@ -97,6 +103,18 @@ struct mcp2221 {
...
@@ -97,6 +103,18 @@ struct mcp2221 {
struct
gpio_chip
*
gc
;
struct
gpio_chip
*
gc
;
u8
gp_idx
;
u8
gp_idx
;
u8
gpio_dir
;
u8
gpio_dir
;
u8
mode
[
4
];
#if IS_REACHABLE(CONFIG_IIO)
struct
iio_chan_spec
iio_channels
[
3
];
u16
adc_values
[
3
];
u8
adc_scale
;
u8
dac_value
;
u16
dac_scale
;
#endif
};
struct
mcp2221_iio
{
struct
mcp2221
*
mcp
;
};
};
/*
/*
...
@@ -567,6 +585,7 @@ static const struct i2c_algorithm mcp_i2c_algo = {
...
@@ -567,6 +585,7 @@ static const struct i2c_algorithm mcp_i2c_algo = {
.
functionality
=
mcp_i2c_func
,
.
functionality
=
mcp_i2c_func
,
};
};
#if IS_REACHABLE(CONFIG_GPIOLIB)
static
int
mcp_gpio_get
(
struct
gpio_chip
*
gc
,
static
int
mcp_gpio_get
(
struct
gpio_chip
*
gc
,
unsigned
int
offset
)
unsigned
int
offset
)
{
{
...
@@ -670,6 +689,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc,
...
@@ -670,6 +689,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc,
return
GPIO_LINE_DIRECTION_OUT
;
return
GPIO_LINE_DIRECTION_OUT
;
}
}
#endif
/* Gives current state of i2c engine inside mcp2221 */
/* Gives current state of i2c engine inside mcp2221 */
static
int
mcp_get_i2c_eng_state
(
struct
mcp2221
*
mcp
,
static
int
mcp_get_i2c_eng_state
(
struct
mcp2221
*
mcp
,
...
@@ -745,6 +765,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
...
@@ -745,6 +765,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
break
;
break
;
}
}
mcp
->
status
=
mcp_get_i2c_eng_state
(
mcp
,
data
,
8
);
mcp
->
status
=
mcp_get_i2c_eng_state
(
mcp
,
data
,
8
);
#if IS_REACHABLE(CONFIG_IIO)
memcpy
(
&
mcp
->
adc_values
,
&
data
[
50
],
sizeof
(
mcp
->
adc_values
));
#endif
break
;
break
;
default:
default:
mcp
->
status
=
-
EIO
;
mcp
->
status
=
-
EIO
;
...
@@ -816,6 +839,69 @@ static int mcp2221_raw_event(struct hid_device *hdev,
...
@@ -816,6 +839,69 @@ static int mcp2221_raw_event(struct hid_device *hdev,
complete
(
&
mcp
->
wait_in_report
);
complete
(
&
mcp
->
wait_in_report
);
break
;
break
;
case
MCP2221_SET_SRAM_SETTINGS
:
switch
(
data
[
1
])
{
case
MCP2221_SUCCESS
:
mcp
->
status
=
0
;
break
;
default:
mcp
->
status
=
-
EAGAIN
;
}
complete
(
&
mcp
->
wait_in_report
);
break
;
case
MCP2221_GET_SRAM_SETTINGS
:
switch
(
data
[
1
])
{
case
MCP2221_SUCCESS
:
memcpy
(
&
mcp
->
mode
,
&
data
[
22
],
4
);
#if IS_REACHABLE(CONFIG_IIO)
mcp
->
dac_value
=
data
[
6
]
&
GENMASK
(
4
,
0
);
#endif
mcp
->
status
=
0
;
break
;
default:
mcp
->
status
=
-
EAGAIN
;
}
complete
(
&
mcp
->
wait_in_report
);
break
;
case
MCP2221_READ_FLASH_DATA
:
switch
(
data
[
1
])
{
case
MCP2221_SUCCESS
:
mcp
->
status
=
0
;
/* Only handles CHIP SETTINGS subpage currently */
if
(
mcp
->
txbuf
[
1
]
!=
0
)
{
mcp
->
status
=
-
EIO
;
break
;
}
#if IS_REACHABLE(CONFIG_IIO)
{
u8
tmp
;
/* DAC scale value */
tmp
=
FIELD_GET
(
GENMASK
(
7
,
6
),
data
[
6
]);
if
((
data
[
6
]
&
BIT
(
5
))
&&
tmp
)
mcp
->
dac_scale
=
tmp
+
4
;
else
mcp
->
dac_scale
=
5
;
/* ADC scale value */
tmp
=
FIELD_GET
(
GENMASK
(
4
,
3
),
data
[
7
]);
if
((
data
[
7
]
&
BIT
(
2
))
&&
tmp
)
mcp
->
adc_scale
=
tmp
-
1
;
else
mcp
->
adc_scale
=
0
;
}
#endif
break
;
default:
mcp
->
status
=
-
EAGAIN
;
}
complete
(
&
mcp
->
wait_in_report
);
break
;
default:
default:
mcp
->
status
=
-
EIO
;
mcp
->
status
=
-
EIO
;
complete
(
&
mcp
->
wait_in_report
);
complete
(
&
mcp
->
wait_in_report
);
...
@@ -824,6 +910,190 @@ static int mcp2221_raw_event(struct hid_device *hdev,
...
@@ -824,6 +910,190 @@ static int mcp2221_raw_event(struct hid_device *hdev,
return
1
;
return
1
;
}
}
/* Device resource managed function for HID unregistration */
static
void
mcp2221_hid_unregister
(
void
*
ptr
)
{
struct
hid_device
*
hdev
=
ptr
;
hid_hw_close
(
hdev
);
hid_hw_stop
(
hdev
);
}
/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
static
void
mcp2221_remove
(
struct
hid_device
*
hdev
)
{
}
#if IS_REACHABLE(CONFIG_IIO)
static
int
mcp2221_read_raw
(
struct
iio_dev
*
indio_dev
,
struct
iio_chan_spec
const
*
channel
,
int
*
val
,
int
*
val2
,
long
mask
)
{
struct
mcp2221_iio
*
priv
=
iio_priv
(
indio_dev
);
struct
mcp2221
*
mcp
=
priv
->
mcp
;
int
ret
;
if
(
mask
==
IIO_CHAN_INFO_SCALE
)
{
if
(
channel
->
output
)
*
val
=
1
<<
mcp
->
dac_scale
;
else
*
val
=
1
<<
mcp
->
adc_scale
;
return
IIO_VAL_INT
;
}
mutex_lock
(
&
mcp
->
lock
);
if
(
channel
->
output
)
{
*
val
=
mcp
->
dac_value
;
ret
=
IIO_VAL_INT
;
}
else
{
/* Read ADC values */
ret
=
mcp_chk_last_cmd_status
(
mcp
);
if
(
!
ret
)
{
*
val
=
le16_to_cpu
((
__force
__le16
)
mcp
->
adc_values
[
channel
->
address
]);
if
(
*
val
>=
BIT
(
10
))
ret
=
-
EINVAL
;
else
ret
=
IIO_VAL_INT
;
}
}
mutex_unlock
(
&
mcp
->
lock
);
return
ret
;
}
static
int
mcp2221_write_raw
(
struct
iio_dev
*
indio_dev
,
struct
iio_chan_spec
const
*
chan
,
int
val
,
int
val2
,
long
mask
)
{
struct
mcp2221_iio
*
priv
=
iio_priv
(
indio_dev
);
struct
mcp2221
*
mcp
=
priv
->
mcp
;
int
ret
;
if
(
val
<
0
||
val
>=
BIT
(
5
))
return
-
EINVAL
;
mutex_lock
(
&
mcp
->
lock
);
memset
(
mcp
->
txbuf
,
0
,
12
);
mcp
->
txbuf
[
0
]
=
MCP2221_SET_SRAM_SETTINGS
;
mcp
->
txbuf
[
4
]
=
BIT
(
7
)
|
val
;
ret
=
mcp_send_data_req_status
(
mcp
,
mcp
->
txbuf
,
12
);
if
(
!
ret
)
mcp
->
dac_value
=
val
;
mutex_unlock
(
&
mcp
->
lock
);
return
ret
;
}
static
const
struct
iio_info
mcp2221_info
=
{
.
read_raw
=
&
mcp2221_read_raw
,
.
write_raw
=
&
mcp2221_write_raw
,
};
static
int
mcp_iio_channels
(
struct
mcp2221
*
mcp
)
{
int
idx
,
cnt
=
0
;
bool
dac_created
=
false
;
/* GP0 doesn't have ADC/DAC alternative function */
for
(
idx
=
1
;
idx
<
MCP_NGPIO
;
idx
++
)
{
struct
iio_chan_spec
*
chan
=
&
mcp
->
iio_channels
[
cnt
];
switch
(
mcp
->
mode
[
idx
])
{
case
2
:
chan
->
address
=
idx
-
1
;
chan
->
channel
=
cnt
++
;
break
;
case
3
:
/* GP1 doesn't have DAC alternative function */
if
(
idx
==
1
||
dac_created
)
continue
;
/* DAC1 and DAC2 outputs are connected to the same DAC */
dac_created
=
true
;
chan
->
output
=
1
;
cnt
++
;
break
;
default:
continue
;
};
chan
->
type
=
IIO_VOLTAGE
;
chan
->
indexed
=
1
;
chan
->
info_mask_separate
=
BIT
(
IIO_CHAN_INFO_RAW
);
chan
->
info_mask_shared_by_type
=
BIT
(
IIO_CHAN_INFO_SCALE
);
chan
->
scan_index
=
-
1
;
}
return
cnt
;
}
static
void
mcp_init_work
(
struct
work_struct
*
work
)
{
struct
iio_dev
*
indio_dev
;
struct
mcp2221
*
mcp
=
container_of
(
work
,
struct
mcp2221
,
init_work
.
work
);
struct
mcp2221_iio
*
data
;
static
int
retries
=
5
;
int
ret
,
num_channels
;
hid_hw_power
(
mcp
->
hdev
,
PM_HINT_FULLON
);
mutex_lock
(
&
mcp
->
lock
);
mcp
->
txbuf
[
0
]
=
MCP2221_GET_SRAM_SETTINGS
;
ret
=
mcp_send_data_req_status
(
mcp
,
mcp
->
txbuf
,
1
);
if
(
ret
==
-
EAGAIN
)
goto
reschedule_task
;
num_channels
=
mcp_iio_channels
(
mcp
);
if
(
!
num_channels
)
goto
unlock
;
mcp
->
txbuf
[
0
]
=
MCP2221_READ_FLASH_DATA
;
mcp
->
txbuf
[
1
]
=
0
;
ret
=
mcp_send_data_req_status
(
mcp
,
mcp
->
txbuf
,
2
);
if
(
ret
==
-
EAGAIN
)
goto
reschedule_task
;
indio_dev
=
devm_iio_device_alloc
(
&
mcp
->
hdev
->
dev
,
sizeof
(
*
data
));
if
(
!
indio_dev
)
goto
unlock
;
data
=
iio_priv
(
indio_dev
);
data
->
mcp
=
mcp
;
indio_dev
->
name
=
"mcp2221"
;
indio_dev
->
modes
=
INDIO_DIRECT_MODE
;
indio_dev
->
info
=
&
mcp2221_info
;
indio_dev
->
channels
=
mcp
->
iio_channels
;
indio_dev
->
num_channels
=
num_channels
;
devm_iio_device_register
(
&
mcp
->
hdev
->
dev
,
indio_dev
);
unlock:
mutex_unlock
(
&
mcp
->
lock
);
hid_hw_power
(
mcp
->
hdev
,
PM_HINT_NORMAL
);
return
;
reschedule_task:
mutex_unlock
(
&
mcp
->
lock
);
hid_hw_power
(
mcp
->
hdev
,
PM_HINT_NORMAL
);
if
(
!
retries
--
)
return
;
/* Device is not ready to read SRAM or FLASH data, try again */
schedule_delayed_work
(
&
mcp
->
init_work
,
msecs_to_jiffies
(
100
));
}
#endif
static
int
mcp2221_probe
(
struct
hid_device
*
hdev
,
static
int
mcp2221_probe
(
struct
hid_device
*
hdev
,
const
struct
hid_device_id
*
id
)
const
struct
hid_device_id
*
id
)
{
{
...
@@ -849,7 +1119,8 @@ static int mcp2221_probe(struct hid_device *hdev,
...
@@ -849,7 +1119,8 @@ static int mcp2221_probe(struct hid_device *hdev,
ret
=
hid_hw_open
(
hdev
);
ret
=
hid_hw_open
(
hdev
);
if
(
ret
)
{
if
(
ret
)
{
hid_err
(
hdev
,
"can't open device
\n
"
);
hid_err
(
hdev
,
"can't open device
\n
"
);
goto
err_hstop
;
hid_hw_stop
(
hdev
);
return
ret
;
}
}
mutex_init
(
&
mcp
->
lock
);
mutex_init
(
&
mcp
->
lock
);
...
@@ -857,6 +1128,10 @@ static int mcp2221_probe(struct hid_device *hdev,
...
@@ -857,6 +1128,10 @@ static int mcp2221_probe(struct hid_device *hdev,
hid_set_drvdata
(
hdev
,
mcp
);
hid_set_drvdata
(
hdev
,
mcp
);
mcp
->
hdev
=
hdev
;
mcp
->
hdev
=
hdev
;
ret
=
devm_add_action_or_reset
(
&
hdev
->
dev
,
mcp2221_hid_unregister
,
hdev
);
if
(
ret
)
return
ret
;
/* Set I2C bus clock diviser */
/* Set I2C bus clock diviser */
if
(
i2c_clk_freq
>
400
)
if
(
i2c_clk_freq
>
400
)
i2c_clk_freq
=
400
;
i2c_clk_freq
=
400
;
...
@@ -873,19 +1148,18 @@ static int mcp2221_probe(struct hid_device *hdev,
...
@@ -873,19 +1148,18 @@ static int mcp2221_probe(struct hid_device *hdev,
"MCP2221 usb-i2c bridge on hidraw%d"
,
"MCP2221 usb-i2c bridge on hidraw%d"
,
((
struct
hidraw
*
)
hdev
->
hidraw
)
->
minor
);
((
struct
hidraw
*
)
hdev
->
hidraw
)
->
minor
);
ret
=
i2c_add_adapter
(
&
mcp
->
adapter
);
ret
=
devm_i2c_add_adapter
(
&
hdev
->
dev
,
&
mcp
->
adapter
);
if
(
ret
)
{
if
(
ret
)
{
hid_err
(
hdev
,
"can't add usb-i2c adapter: %d
\n
"
,
ret
);
hid_err
(
hdev
,
"can't add usb-i2c adapter: %d
\n
"
,
ret
);
goto
err_i2c
;
return
ret
;
}
}
i2c_set_adapdata
(
&
mcp
->
adapter
,
mcp
);
i2c_set_adapdata
(
&
mcp
->
adapter
,
mcp
);
#if IS_REACHABLE(CONFIG_GPIOLIB)
/* Setup GPIO chip */
/* Setup GPIO chip */
mcp
->
gc
=
devm_kzalloc
(
&
hdev
->
dev
,
sizeof
(
*
mcp
->
gc
),
GFP_KERNEL
);
mcp
->
gc
=
devm_kzalloc
(
&
hdev
->
dev
,
sizeof
(
*
mcp
->
gc
),
GFP_KERNEL
);
if
(
!
mcp
->
gc
)
{
if
(
!
mcp
->
gc
)
ret
=
-
ENOMEM
;
return
-
ENOMEM
;
goto
err_gc
;
}
mcp
->
gc
->
label
=
"mcp2221_gpio"
;
mcp
->
gc
->
label
=
"mcp2221_gpio"
;
mcp
->
gc
->
direction_input
=
mcp_gpio_direction_input
;
mcp
->
gc
->
direction_input
=
mcp_gpio_direction_input
;
...
@@ -900,26 +1174,15 @@ static int mcp2221_probe(struct hid_device *hdev,
...
@@ -900,26 +1174,15 @@ static int mcp2221_probe(struct hid_device *hdev,
ret
=
devm_gpiochip_add_data
(
&
hdev
->
dev
,
mcp
->
gc
,
mcp
);
ret
=
devm_gpiochip_add_data
(
&
hdev
->
dev
,
mcp
->
gc
,
mcp
);
if
(
ret
)
if
(
ret
)
goto
err_gc
;
return
ret
;
#endif
return
0
;
err_gc:
i2c_del_adapter
(
&
mcp
->
adapter
);
err_i2c:
hid_hw_close
(
mcp
->
hdev
);
err_hstop:
hid_hw_stop
(
mcp
->
hdev
);
return
ret
;
}
static
void
mcp2221_remove
(
struct
hid_device
*
hdev
)
#if IS_REACHABLE(CONFIG_IIO)
{
INIT_DELAYED_WORK
(
&
mcp
->
init_work
,
mcp_init_work
);
struct
mcp2221
*
mcp
=
hid_get_drvdata
(
hdev
);
schedule_delayed_work
(
&
mcp
->
init_work
,
msecs_to_jiffies
(
100
));
#endif
i2c_del_adapter
(
&
mcp
->
adapter
);
return
0
;
hid_hw_close
(
mcp
->
hdev
);
hid_hw_stop
(
mcp
->
hdev
);
}
}
static
const
struct
hid_device_id
mcp2221_devices
[]
=
{
static
const
struct
hid_device_id
mcp2221_devices
[]
=
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment