mirror of https://gitee.com/openkylin/linux.git
Staging drivers patches for 3.20-rc1
Here's the big staging driver tree update for 3.20-rc1. Lots of little things in here, adding up to lots of overall cleanups. The IIO driver updates are also in here as they cross the staging tree boundry a lot. I2O has moved into staging as well, as a plan to drop it from the tree eventually as that's a dead subsystem. All of this has been in linux-next with no reported issues for a while. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlTgtVQACgkQMUfUDdst+yk4mACgshYZ1fOQDoPR+BXd+QD1HXfh GosAoICXkSjDQjwVo13W6QHIVMsUezY+ =4jHr -----END PGP SIGNATURE----- Merge tag 'staging-3.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging drivers patches from Greg KH: "Here's the big staging driver tree update for 3.20-rc1. Lots of little things in here, adding up to lots of overall cleanups. The IIO driver updates are also in here as they cross the staging tree boundry a lot. I2O has moved into staging as well, as a plan to drop it from the tree eventually as that's a dead subsystem. All of this has been in linux-next with no reported issues for a while" * tag 'staging-3.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (740 commits) staging: lustre: lustre: libcfs: define symbols as static staging: rtl8712: Do coding style cleanup staging: lustre: make obd_updatemax_lock static staging: rtl8188eu: core: switch with redundant cases staging: rtl8188eu: odm: conditional setting with no effect staging: rtl8188eu: odm: condition with no effect staging: ft1000: fix braces warning staging: sm7xxfb: fix remaining CamelCase staging: sm7xxfb: fix CamelCase staging: rtl8723au: multiple condition with no effect - if identical to else staging: sm7xxfb: make smtc_scr_info static staging/lustre/mdc: Initialize req in mdc_enqueue for !it case staging/lustre/clio: Do not allow group locks with gid 0 staging/lustre/llite: don't add to page cache upon failure staging/lustre/llite: Add exception entry check after radix_tree staging/lustre/libcfs: protect kkuc_groups from write access staging/lustre/fld: refer to MDT0 for fld lookup in some cases staging/lustre/llite: Solve a race to access lli_has_smd in read case staging/lustre/ptlrpc: hold rq_lock when modify rq_flags staging/lustre/lnet: portal spreading rotor should be unsigned ...
This commit is contained in:
commit
46f7b63556
|
@ -92,6 +92,18 @@ Description:
|
|||
is required is a consistent labeling. Units after application
|
||||
of scale and offset are millivolts.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_currentY_raw
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_currentY_supply_raw
|
||||
KernelVersion: 3.17
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Raw (unscaled no bias removal etc.) current measurement from
|
||||
channel Y. In special cases where the channel does not
|
||||
correspond to externally available input one of the named
|
||||
versions may be used. The number must always be specified and
|
||||
unique to allow association with event codes. Units after
|
||||
application of scale and offset are milliamps.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_capacitanceY_raw
|
||||
KernelVersion: 3.2
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
|
@ -234,6 +246,8 @@ What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_offset
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_voltage_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_currentY_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_current_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_tempY_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_temp_offset
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_pressureY_offset
|
||||
|
@ -262,9 +276,14 @@ What: /sys/bus/iio/devices/iio:deviceX/in_voltage_scale
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_voltage-voltage_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_currentY_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_currentY_supply_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_current_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_accel_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_accel_peak_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_energy_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_distance_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_magn_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_magn_x_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_magn_y_scale
|
||||
|
@ -276,6 +295,7 @@ What: /sys/bus/iio/devices/iio:deviceX/in_rot_from_north_true_tilt_comp_scale
|
|||
What: /sys/bus/iio/devices/iio:deviceX/in_pressureY_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_pressure_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_humidityrelative_scale
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_scale
|
||||
KernelVersion: 2.6.35
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
|
@ -323,6 +343,44 @@ Description:
|
|||
production inaccuracies). If shared across all channels,
|
||||
<type>_calibscale is used.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_activity_calibgender
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_energy_calibgender
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_distance_calibgender
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_velocity_calibgender
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Gender of the user (e.g.: male, female) used by some pedometers
|
||||
to compute the stride length, distance, speed and activity
|
||||
type.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_activity_calibgender_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_energy_calibgender_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_distance_calibgender_available
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_velocity_calibgender_available
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Lists all available gender values (e.g.: male, female).
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_activity_calibheight
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_energy_calibheight
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_distance_calibheight
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_velocity_calibheight
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Height of the user (in meters) used by some pedometers
|
||||
to compute the stride length, distance, speed and activity
|
||||
type.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_energy_calibweight
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Weight of the user (in kg). It is needed by some pedometers
|
||||
to compute the calories burnt by the user.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_accel_scale_available
|
||||
What: /sys/.../iio:deviceX/in_voltageX_scale_available
|
||||
What: /sys/.../iio:deviceX/in_voltage-voltage_scale_available
|
||||
|
@ -783,6 +841,14 @@ What: /sys/.../events/in_tempY_roc_falling_period
|
|||
What: /sys/.../events/in_accel_x&y&z_mag_falling_period
|
||||
What: /sys/.../events/in_intensity0_thresh_period
|
||||
What: /sys/.../events/in_proximity0_thresh_period
|
||||
What: /sys/.../events/in_activity_still_thresh_rising_period
|
||||
What: /sys/.../events/in_activity_still_thresh_falling_period
|
||||
What: /sys/.../events/in_activity_walking_thresh_rising_period
|
||||
What: /sys/.../events/in_activity_walking_thresh_falling_period
|
||||
What: /sys/.../events/in_activity_jogging_thresh_rising_period
|
||||
What: /sys/.../events/in_activity_jogging_thresh_falling_period
|
||||
What: /sys/.../events/in_activity_running_thresh_rising_period
|
||||
What: /sys/.../events/in_activity_running_thresh_falling_period
|
||||
KernelVersion: 2.6.37
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
|
@ -790,6 +856,40 @@ Description:
|
|||
met before an event is generated. If direction is not
|
||||
specified then this period applies to both directions.
|
||||
|
||||
What: /sys/.../events/in_activity_still_thresh_rising_en
|
||||
What: /sys/.../events/in_activity_still_thresh_falling_en
|
||||
What: /sys/.../events/in_activity_walking_thresh_rising_en
|
||||
What: /sys/.../events/in_activity_walking_thresh_falling_en
|
||||
What: /sys/.../events/in_activity_jogging_thresh_rising_en
|
||||
What: /sys/.../events/in_activity_jogging_thresh_falling_en
|
||||
What: /sys/.../events/in_activity_running_thresh_rising_en
|
||||
What: /sys/.../events/in_activity_running_thresh_falling_en
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Enables or disables activitity events. Depending on direction
|
||||
an event is generated when sensor ENTERS or LEAVES a given state.
|
||||
|
||||
What: /sys/.../events/in_activity_still_thresh_rising_value
|
||||
What: /sys/.../events/in_activity_still_thresh_falling_value
|
||||
What: /sys/.../events/in_activity_walking_thresh_rising_value
|
||||
What: /sys/.../events/in_activity_walking_thresh_falling_value
|
||||
What: /sys/.../events/in_activity_jogging_thresh_rising_value
|
||||
What: /sys/.../events/in_activity_jogging_thresh_falling_value
|
||||
What: /sys/.../events/in_activity_running_thresh_rising_value
|
||||
What: /sys/.../events/in_activity_running_thresh_falling_value
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Confidence value (in units as percentage) to be used
|
||||
for deciding when an event should be generated. E.g for
|
||||
running: If the confidence value reported by the sensor
|
||||
is greater than in_activity_running_thresh_rising_value
|
||||
then the sensor ENTERS running state. Conversely, if the
|
||||
confidence value reported by the sensor is lower than
|
||||
in_activity_running_thresh_falling_value then the sensor
|
||||
is LEAVING running state.
|
||||
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_rising_en
|
||||
What: /sys/.../iio:deviceX/events/in_accel_mag_falling_en
|
||||
|
@ -822,6 +922,25 @@ Description:
|
|||
number or direction is not specified, applies to all channels of
|
||||
this type.
|
||||
|
||||
What: /sys/.../events/in_steps_change_en
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Event generated when channel passes a threshold on the absolute
|
||||
change in value. E.g. for steps: a step change event is
|
||||
generated each time the user takes N steps, where N is set using
|
||||
in_steps_change_value.
|
||||
|
||||
What: /sys/.../events/in_steps_change_value
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Specifies the value of change threshold that the
|
||||
device is comparing against for the events enabled by
|
||||
<type>[Y][_name]_roc[_rising|falling|]_en. E.g. for steps:
|
||||
if set to 3, a step change event will be generated every 3
|
||||
steps.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/trigger/current_trigger
|
||||
KernelVersion: 2.6.35
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
|
@ -956,6 +1075,16 @@ Description:
|
|||
and the relevant _type attributes to establish the data storage
|
||||
format.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_activity_still_input
|
||||
What: /sys/.../iio:deviceX/in_activity_walking_input
|
||||
What: /sys/.../iio:deviceX/in_activity_jogging_input
|
||||
What: /sys/.../iio:deviceX/in_activity_running_input
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute is used to read the confidence for an activity
|
||||
expressed in units as percentage.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_anglvel_z_quadrature_correction_raw
|
||||
KernelVersion: 2.6.38
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
|
@ -973,6 +1102,24 @@ Description:
|
|||
For a list of available output power modes read
|
||||
in_accel_power_mode_available.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_energy_input
|
||||
What: /sys/.../iio:deviceX/in_energy_raw
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute is used to read the energy value reported by the
|
||||
device (e.g.: human activity sensors report energy burnt by the
|
||||
user). Units after application of scale are Joules.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_distance_input
|
||||
What: /sys/.../iio:deviceX/in_distance_raw
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute is used to read the distance covered by the user
|
||||
since the last reboot while activated. Units after application
|
||||
of scale are meters.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/store_eeprom
|
||||
KernelVersion: 3.4.0
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
|
@ -992,7 +1139,9 @@ Description:
|
|||
reflectivity of infrared or ultrasound emitted.
|
||||
Often these sensors are unit less and as such conversion
|
||||
to SI units is not possible. Where it is, the units should
|
||||
be meters.
|
||||
be meters. If such a conversion is not possible, the reported
|
||||
values should behave in the same way as a distance, i.e. lower
|
||||
values indicate something is closer to the sensor.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_illuminanceY_input
|
||||
What: /sys/.../iio:deviceX/in_illuminanceY_raw
|
||||
|
@ -1024,6 +1173,12 @@ Description:
|
|||
This attribute is used to get/set the integration time in
|
||||
seconds.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_integration_time
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Number of seconds in which to compute speed.
|
||||
|
||||
What: /sys/bus/iio/devices/iio:deviceX/in_rot_quaternion_raw
|
||||
KernelVersion: 3.15
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
|
@ -1051,3 +1206,46 @@ Description:
|
|||
after application of scale and offset. If no offset or scale is
|
||||
present, output should be considered as processed with the
|
||||
unit in milliamps.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_energy_en
|
||||
What: /sys/.../iio:deviceX/in_distance_en
|
||||
What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_en
|
||||
What: /sys/.../iio:deviceX/in_steps_en
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Activates a device feature that runs in firmware/hardware.
|
||||
E.g. for steps: the pedometer saves power while not used;
|
||||
when activated, it will count the steps taken by the user in
|
||||
firmware and export them through in_steps_input.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_steps_input
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute is used to read the number of steps taken by the user
|
||||
since the last reboot while activated.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_input
|
||||
What: /sys/.../iio:deviceX/in_velocity_sqrt(x^2+y^2+z^2)_raw
|
||||
KernelVersion: 3.19
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
This attribute is used to read the current speed value of the
|
||||
user (which is the norm or magnitude of the velocity vector).
|
||||
Units after application of scale are m/s.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_steps_debounce_count
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Specifies the number of steps that must occur within
|
||||
in_steps_filter_debounce_time for the pedometer to decide the
|
||||
consumer is making steps.
|
||||
|
||||
What: /sys/.../iio:deviceX/in_steps_debounce_time
|
||||
KernelVersion: 3.20
|
||||
Contact: linux-iio@vger.kernel.org
|
||||
Description:
|
||||
Specifies number of seconds in which we compute the steps
|
||||
that occur in order to decide if the consumer is making steps.
|
||||
|
|
|
@ -35,6 +35,7 @@ atmel,24c512 i2c serial eeprom (24cxx)
|
|||
atmel,24c1024 i2c serial eeprom (24cxx)
|
||||
atmel,at97sc3204t i2c trusted platform module (TPM)
|
||||
capella,cm32181 CM32181: Ambient Light Sensor
|
||||
capella,cm3232 CM3232: Ambient Light Sensor
|
||||
catalyst,24c32 i2c serial eeprom
|
||||
cirrus,cs42l51 Cirrus Logic CS42L51 audio codec
|
||||
dallas,ds1307 64 x 8, Serial, I2C Real-Time Clock
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
* Cosmic Circuits - Analog to Digital Converter (CC-10001-ADC)
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "cosmic,10001-adc"
|
||||
- reg: Should contain adc registers location and length.
|
||||
- clock-names: Should contain "adc".
|
||||
- clocks: Should contain a clock specifier for each entry in clock-names
|
||||
- vref-supply: The regulator supply ADC reference voltage.
|
||||
|
||||
Optional properties:
|
||||
- adc-reserved-channels: Bitmask of reserved channels,
|
||||
i.e. channels that cannot be used by the OS.
|
||||
|
||||
Example:
|
||||
adc: adc@18101600 {
|
||||
compatible = "cosmic,10001-adc";
|
||||
reg = <0x18101600 0x24>;
|
||||
adc-reserved-channels = <0x2>;
|
||||
clocks = <&adc_clk>;
|
||||
clock-names = "adc";
|
||||
vref-supply = <®_1v8>;
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
Qualcomm's SPMI PMIC voltage ADC
|
||||
|
||||
SPMI PMIC voltage ADC (VADC) provides interface to clients to read
|
||||
voltage. The VADC is a 15-bit sigma-delta ADC.
|
||||
|
||||
VADC node:
|
||||
|
||||
- compatible:
|
||||
Usage: required
|
||||
Value type: <string>
|
||||
Definition: Should contain "qcom,spmi-vadc".
|
||||
|
||||
- reg:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: VADC base address and length in the SPMI PMIC register map.
|
||||
|
||||
- #address-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Must be one. Child node 'reg' property should define ADC
|
||||
channel number.
|
||||
|
||||
- #size-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Must be zero.
|
||||
|
||||
- #io-channel-cells:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: Must be one. For details about IIO bindings see:
|
||||
Documentation/devicetree/bindings/iio/iio-bindings.txt
|
||||
|
||||
- interrupts:
|
||||
Usage: optional
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: End of conversion interrupt.
|
||||
|
||||
Channel node properties:
|
||||
|
||||
- reg:
|
||||
Usage: required
|
||||
Value type: <u32>
|
||||
Definition: ADC channel number.
|
||||
See include/dt-bindings/iio/qcom,spmi-vadc.h
|
||||
|
||||
- qcom,decimation:
|
||||
Usage: optional
|
||||
Value type: <u32>
|
||||
Definition: This parameter is used to decrease ADC sampling rate.
|
||||
Quicker measurements can be made by reducing decimation ratio.
|
||||
Valid values are 512, 1024, 2048, 4096.
|
||||
If property is not found, default value of 512 will be used.
|
||||
|
||||
- qcom,pre-scaling:
|
||||
Usage: optional
|
||||
Value type: <u32 array>
|
||||
Definition: Used for scaling the channel input signal before the signal is
|
||||
fed to VADC. The configuration for this node is to know the
|
||||
pre-determined ratio and use it for post scaling. Select one from
|
||||
the following options.
|
||||
<1 1>, <1 3>, <1 4>, <1 6>, <1 20>, <1 8>, <10 81>, <1 10>
|
||||
If property is not found default value depending on chip will be used.
|
||||
|
||||
- qcom,ratiometric:
|
||||
Usage: optional
|
||||
Value type: <empty>
|
||||
Definition: Channel calibration type. If this property is specified
|
||||
VADC will use the VDD reference (1.8V) and GND for channel
|
||||
calibration. If property is not found, channel will be
|
||||
calibrated with 0.625V and 1.25V reference channels, also
|
||||
known as absolute calibration.
|
||||
|
||||
- qcom,hw-settle-time:
|
||||
Usage: optional
|
||||
Value type: <u32>
|
||||
Definition: Time between AMUX getting configured and the ADC starting
|
||||
conversion. Delay = 100us * (value) for value < 11, and
|
||||
2ms * (value - 10) otherwise.
|
||||
Valid values are: 0, 100, 200, 300, 400, 500, 600, 700, 800,
|
||||
900 us and 1, 2, 4, 6, 8, 10 ms
|
||||
If property is not found, channel will use 0us.
|
||||
|
||||
- qcom,avg-samples:
|
||||
Usage: optional
|
||||
Value type: <u32>
|
||||
Definition: Number of samples to be used for measurement.
|
||||
Averaging provides the option to obtain a single measurement
|
||||
from the ADC that is an average of multiple samples. The value
|
||||
selected is 2^(value).
|
||||
Valid values are: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
|
||||
If property is not found, 1 sample will be used.
|
||||
|
||||
NOTE:
|
||||
|
||||
Following channels, also known as reference point channels, are used for
|
||||
result calibration and their channel configuration nodes should be defined:
|
||||
VADC_REF_625MV and/or VADC_SPARE1(based on PMIC version) VADC_REF_1250MV,
|
||||
VADC_GND_REF and VADC_VDD_VADC.
|
||||
|
||||
Example:
|
||||
|
||||
/* VADC node */
|
||||
pmic_vadc: vadc@3100 {
|
||||
compatible = "qcom,spmi-vadc";
|
||||
reg = <0x3100 0x100>;
|
||||
interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
#io-channel-cells = <1>;
|
||||
io-channel-ranges;
|
||||
|
||||
/* Channel node */
|
||||
usb_id_nopull {
|
||||
reg = <VADC_LR_MUX10_USB_ID>;
|
||||
qcom,decimation = <512>;
|
||||
qcom,ratiometric;
|
||||
qcom,hw-settle-time = <200>;
|
||||
qcom,avg-samples = <1>;
|
||||
qcom,pre-scaling = <1 3>;
|
||||
};
|
||||
};
|
||||
|
||||
/* IIO client node */
|
||||
usb {
|
||||
io-channels = <&pmic_vadc VADC_LR_MUX10_USB_ID>;
|
||||
io-channel-names = "vadc";
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
Samsung Sensorhub driver
|
||||
|
||||
Sensorhub is a MCU which manages several sensors and also plays the role
|
||||
of a virtual sensor device.
|
||||
|
||||
Required properties:
|
||||
- compatible: "samsung,sensorhub-rinato" or "samsung,sensorhub-thermostat"
|
||||
- spi-max-frequency: max SPI clock frequency
|
||||
- interrupt-parent: interrupt parent
|
||||
- interrupts: communication interrupt
|
||||
- ap-mcu-gpios: [out] ap to sensorhub line - used during communication
|
||||
- mcu-ap-gpios: [in] sensorhub to ap - used during communication
|
||||
- mcu-reset-gpios: [out] sensorhub reset
|
||||
|
||||
Example:
|
||||
|
||||
shub_spi: shub {
|
||||
compatible = "samsung,sensorhub-rinato";
|
||||
spi-max-frequency = <5000000>;
|
||||
interrupt-parent = <&gpx0>;
|
||||
interrupts = <2 0>;
|
||||
ap-mcu-gpios = <&gpx0 0 0>;
|
||||
mcu-ap-gpios = <&gpx0 4 0>;
|
||||
mcu-reset-gpios = <&gpx0 5 0>;
|
||||
};
|
|
@ -12,9 +12,9 @@ Optional properties:
|
|||
property is not present, then the touchscreen is
|
||||
disabled. 5 wires is valid for i.MX28 SoC only.
|
||||
- fsl,ave-ctrl: number of samples per direction to calculate an average value.
|
||||
Allowed value is 1 ... 31, default is 4
|
||||
Allowed value is 1 ... 32, default is 4
|
||||
- fsl,ave-delay: delay between consecutive samples. Allowed value is
|
||||
1 ... 2047. It is used if 'fsl,ave-ctrl' > 1, counts at
|
||||
2 ... 2048. It is used if 'fsl,ave-ctrl' > 1, counts at
|
||||
2 kHz and its default is 2 (= 1 ms)
|
||||
- fsl,settling: delay between plate switch to next sample. Allowed value is
|
||||
1 ... 2047. It counts at 2 kHz and its default is
|
||||
|
|
|
@ -38,6 +38,7 @@ chunghwa Chunghwa Picture Tubes Ltd.
|
|||
cirrus Cirrus Logic, Inc.
|
||||
cnm Chips&Media, Inc.
|
||||
cortina Cortina Systems, Inc.
|
||||
cosmic Cosmic Circuits
|
||||
crystalfontz Crystalfontz America, Inc.
|
||||
dallas Maxim Integrated Products (formerly Dallas Semiconductor)
|
||||
davicom DAVICOM Semiconductor, Inc.
|
||||
|
|
|
@ -258,6 +258,8 @@ IIO
|
|||
devm_iio_device_free()
|
||||
devm_iio_device_register()
|
||||
devm_iio_device_unregister()
|
||||
devm_iio_kfifo_allocate()
|
||||
devm_iio_kfifo_free()
|
||||
devm_iio_trigger_alloc()
|
||||
devm_iio_trigger_free()
|
||||
|
||||
|
|
20
MAINTAINERS
20
MAINTAINERS
|
@ -2399,6 +2399,12 @@ F: security/capability.c
|
|||
F: security/commoncap.c
|
||||
F: kernel/capability.c
|
||||
|
||||
CAPELLA MICROSYSTEMS LIGHT SENSOR DRIVER
|
||||
M: Kevin Tsai <ktsai@capellamicro.com>
|
||||
S: Maintained
|
||||
F: drivers/iio/light/cm*
|
||||
F: Documentation/devicetree/bindings/i2c/trivial-devices.txt
|
||||
|
||||
CC2520 IEEE-802.15.4 RADIO DRIVER
|
||||
M: Varka Bhadram <varkabhadram@gmail.com>
|
||||
L: linux-wpan@vger.kernel.org
|
||||
|
@ -3904,6 +3910,12 @@ S: Supported
|
|||
F: Documentation/fault-injection/
|
||||
F: lib/fault-inject.c
|
||||
|
||||
FBTFT Framebuffer drivers
|
||||
M: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
|
||||
M: Noralf Trønnes <noralf@tronnes.org>
|
||||
S: Maintained
|
||||
F: drivers/staging/fbtft/
|
||||
|
||||
FCOE SUBSYSTEM (libfc, libfcoe, fcoe)
|
||||
M: Robert Love <robert.w.love@intel.com>
|
||||
L: fcoe-devel@open-fcoe.org
|
||||
|
@ -9262,6 +9274,14 @@ L: linux-wireless@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/staging/rtl8723au/
|
||||
|
||||
STAGING - SILICON MOTION SM7XX FRAME BUFFER DRIVER
|
||||
M: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
|
||||
M: Teddy Wang <teddy.wang@siliconmotion.com>
|
||||
M: Sudip Mukherjee <sudip@vectorindia.org>
|
||||
L: linux-fbdev@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/staging/sm7xxfb/
|
||||
|
||||
STAGING - SLICOSS
|
||||
M: Lior Dotan <liodot@gmail.com>
|
||||
M: Christopher Harrer <charrer@alacritech.com>
|
||||
|
|
|
@ -36,8 +36,6 @@ source "drivers/message/fusion/Kconfig"
|
|||
|
||||
source "drivers/firewire/Kconfig"
|
||||
|
||||
source "drivers/message/i2o/Kconfig"
|
||||
|
||||
source "drivers/macintosh/Kconfig"
|
||||
|
||||
source "drivers/net/Kconfig"
|
||||
|
|
|
@ -27,7 +27,6 @@ boolean "IIO callback buffer used for push in-kernel interfaces"
|
|||
usage. That is, those where the data is pushed to the consumer.
|
||||
|
||||
config IIO_KFIFO_BUF
|
||||
select IIO_TRIGGER
|
||||
tristate "Industrial I/O buffering based on kfifo"
|
||||
help
|
||||
A simple fifo based on kfifo. Note that this currently provides
|
||||
|
|
|
@ -43,6 +43,9 @@ config HID_SENSOR_ACCEL_3D
|
|||
Say yes here to build support for the HID SENSOR
|
||||
accelerometers 3D.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-sensor-accel-3d.
|
||||
|
||||
config IIO_ST_ACCEL_3AXIS
|
||||
tristate "STMicroelectronics accelerometers 3-Axis Driver"
|
||||
depends on (I2C || SPI_MASTER) && SYSFS
|
||||
|
@ -80,6 +83,9 @@ config KXSD9
|
|||
Say yes here to build support for the Kionix KXSD9 accelerometer.
|
||||
Currently this only supports the device via an SPI interface.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called kxsd9.
|
||||
|
||||
config MMA8452
|
||||
tristate "Freescale MMA8452Q Accelerometer Driver"
|
||||
depends on I2C
|
||||
|
@ -105,4 +111,29 @@ config KXCJK1013
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called kxcjk-1013.
|
||||
|
||||
config MMA9551_CORE
|
||||
tristate
|
||||
|
||||
config MMA9551
|
||||
tristate "Freescale MMA9551L Intelligent Motion-Sensing Platform Driver"
|
||||
depends on I2C
|
||||
select MMA9551_CORE
|
||||
|
||||
help
|
||||
Say yes here to build support for the Freescale MMA9551L
|
||||
Intelligent Motion-Sensing Platform Driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called mma9551.
|
||||
|
||||
config MMA9553
|
||||
tristate "Freescale MMA9553L Intelligent Pedometer Platform Driver"
|
||||
depends on I2C
|
||||
select MMA9551_CORE
|
||||
help
|
||||
Say yes here to build support for the Freescale MMA9553L
|
||||
Intelligent Pedometer Platform Driver.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called mma9553.
|
||||
endmenu
|
||||
|
|
|
@ -10,6 +10,12 @@ obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o
|
|||
obj-$(CONFIG_KXSD9) += kxsd9.o
|
||||
obj-$(CONFIG_MMA8452) += mma8452.o
|
||||
|
||||
obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o
|
||||
obj-$(CONFIG_MMA9551) += mma9551.o
|
||||
obj-$(CONFIG_MMA9553) += mma9553.o
|
||||
|
||||
obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_accel_sensor.o
|
||||
|
||||
obj-$(CONFIG_IIO_ST_ACCEL_3AXIS) += st_accel.o
|
||||
st_accel-y := st_accel_core.o
|
||||
st_accel-$(CONFIG_IIO_BUFFER) += st_accel_buffer.o
|
||||
|
|
|
@ -111,19 +111,12 @@ static int accel_3d_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case 0:
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&accel_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&accel_state->common_attributes, true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
report_id = accel_state->accel[chan->scan_index].report_id;
|
||||
address = accel_3d_addresses[chan->scan_index];
|
||||
if (report_id >= 0)
|
||||
|
@ -419,6 +412,7 @@ static struct platform_driver hid_accel_3d_platform_driver = {
|
|||
.id_table = hid_accel_3d_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_accel_3d_probe,
|
||||
.remove = hid_accel_3d_remove,
|
||||
|
|
|
@ -108,6 +108,7 @@ struct kxcjk1013_data {
|
|||
bool motion_trigger_on;
|
||||
int64_t timestamp;
|
||||
enum kx_chipset chipset;
|
||||
bool is_smo8500_device;
|
||||
};
|
||||
|
||||
enum kxcjk1013_axis {
|
||||
|
@ -377,6 +378,7 @@ static int kxcjk1013_get_startup_times(struct kxcjk1013_data *data)
|
|||
|
||||
static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on)
|
||||
{
|
||||
#ifdef CONFIG_PM
|
||||
int ret;
|
||||
|
||||
if (on)
|
||||
|
@ -388,8 +390,11 @@ static int kxcjk1013_set_power_state(struct kxcjk1013_data *data, bool on)
|
|||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"Failed: kxcjk1013_set_power_state for %d\n", on);
|
||||
if (on)
|
||||
pm_runtime_put_noidle(&data->client->dev);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -858,6 +863,8 @@ static int kxcjk1013_write_event_config(struct iio_dev *indio_dev,
|
|||
|
||||
ret = kxcjk1013_setup_any_motion_interrupt(data, state);
|
||||
if (ret < 0) {
|
||||
kxcjk1013_set_power_state(data, false);
|
||||
data->ev_enable_state = 0;
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1008,6 +1015,7 @@ static int kxcjk1013_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
|||
else
|
||||
ret = kxcjk1013_setup_new_data_interrupt(data, state);
|
||||
if (ret < 0) {
|
||||
kxcjk1013_set_power_state(data, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1131,12 +1139,16 @@ static irqreturn_t kxcjk1013_data_rdy_trig_poll(int irq, void *private)
|
|||
}
|
||||
|
||||
static const char *kxcjk1013_match_acpi_device(struct device *dev,
|
||||
enum kx_chipset *chipset)
|
||||
enum kx_chipset *chipset,
|
||||
bool *is_smo8500_device)
|
||||
{
|
||||
const struct acpi_device_id *id;
|
||||
|
||||
id = acpi_match_device(dev->driver->acpi_match_table, dev);
|
||||
if (!id)
|
||||
return NULL;
|
||||
if (strcmp(id->id, "SMO8500") == 0)
|
||||
*is_smo8500_device = true;
|
||||
*chipset = (enum kx_chipset)id->driver_data;
|
||||
|
||||
return dev_name(dev);
|
||||
|
@ -1151,6 +1163,8 @@ static int kxcjk1013_gpio_probe(struct i2c_client *client,
|
|||
|
||||
if (!client)
|
||||
return -EINVAL;
|
||||
if (data->is_smo8500_device)
|
||||
return -ENOTSUPP;
|
||||
|
||||
dev = &client->dev;
|
||||
|
||||
|
@ -1200,7 +1214,8 @@ static int kxcjk1013_probe(struct i2c_client *client,
|
|||
name = id->name;
|
||||
} else if (ACPI_HANDLE(&client->dev)) {
|
||||
name = kxcjk1013_match_acpi_device(&client->dev,
|
||||
&data->chipset);
|
||||
&data->chipset,
|
||||
&data->is_smo8500_device);
|
||||
} else
|
||||
return -ENODEV;
|
||||
|
||||
|
@ -1228,21 +1243,25 @@ static int kxcjk1013_probe(struct i2c_client *client,
|
|||
KXCJK1013_IRQ_NAME,
|
||||
indio_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_poweroff;
|
||||
|
||||
data->dready_trig = devm_iio_trigger_alloc(&client->dev,
|
||||
"%s-dev%d",
|
||||
indio_dev->name,
|
||||
indio_dev->id);
|
||||
if (!data->dready_trig)
|
||||
return -ENOMEM;
|
||||
if (!data->dready_trig) {
|
||||
ret = -ENOMEM;
|
||||
goto err_poweroff;
|
||||
}
|
||||
|
||||
data->motion_trig = devm_iio_trigger_alloc(&client->dev,
|
||||
"%s-any-motion-dev%d",
|
||||
indio_dev->name,
|
||||
indio_dev->id);
|
||||
if (!data->motion_trig)
|
||||
return -ENOMEM;
|
||||
if (!data->motion_trig) {
|
||||
ret = -ENOMEM;
|
||||
goto err_poweroff;
|
||||
}
|
||||
|
||||
data->dready_trig->dev.parent = &client->dev;
|
||||
data->dready_trig->ops = &kxcjk1013_trigger_ops;
|
||||
|
@ -1251,7 +1270,7 @@ static int kxcjk1013_probe(struct i2c_client *client,
|
|||
iio_trigger_get(indio_dev->trig);
|
||||
ret = iio_trigger_register(data->dready_trig);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto err_poweroff;
|
||||
|
||||
data->motion_trig->dev.parent = &client->dev;
|
||||
data->motion_trig->ops = &kxcjk1013_trigger_ops;
|
||||
|
@ -1300,6 +1319,8 @@ static int kxcjk1013_probe(struct i2c_client *client,
|
|||
iio_trigger_unregister(data->dready_trig);
|
||||
if (data->motion_trig)
|
||||
iio_trigger_unregister(data->motion_trig);
|
||||
err_poweroff:
|
||||
kxcjk1013_set_mode(data, STANDBY);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1349,10 +1370,7 @@ static int kxcjk1013_resume(struct device *dev)
|
|||
int ret = 0;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
/* Check, if the suspend occured while active */
|
||||
if (data->dready_trigger_on || data->motion_trigger_on ||
|
||||
data->ev_enable_state)
|
||||
ret = kxcjk1013_set_mode(data, OPERATION);
|
||||
ret = kxcjk1013_set_mode(data, OPERATION);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
|
@ -1364,8 +1382,14 @@ static int kxcjk1013_runtime_suspend(struct device *dev)
|
|||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct kxcjk1013_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
return kxcjk1013_set_mode(data, STANDBY);
|
||||
ret = kxcjk1013_set_mode(data, STANDBY);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "powering off device failed\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kxcjk1013_runtime_resume(struct device *dev)
|
||||
|
@ -1399,6 +1423,7 @@ static const struct acpi_device_id kx_acpi_match[] = {
|
|||
{"KXCJ1013", KXCJK1013},
|
||||
{"KXCJ1008", KXCJ91008},
|
||||
{"KXTJ1009", KXTJ21009},
|
||||
{"SMO8500", KXCJ91008},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, kx_acpi_match);
|
||||
|
@ -1407,6 +1432,7 @@ static const struct i2c_device_id kxcjk1013_id[] = {
|
|||
{"kxcjk1013", KXCJK1013},
|
||||
{"kxcj91008", KXCJ91008},
|
||||
{"kxtj21009", KXTJ21009},
|
||||
{"SMO8500", KXCJ91008},
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ static const int mma8452_samp_freq[8][2] = {
|
|||
{6, 250000}, {1, 560000}
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
* Hardware has fullscale of -2G, -4G, -8G corresponding to raw value -2048
|
||||
* The userspace interface uses m/s^2 and we declare micro units
|
||||
* So scale factor is given by:
|
||||
|
|
|
@ -0,0 +1,637 @@
|
|||
/*
|
||||
* Freescale MMA9551L Intelligent Motion-Sensing Platform driver
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include "mma9551_core.h"
|
||||
|
||||
#define MMA9551_DRV_NAME "mma9551"
|
||||
#define MMA9551_IRQ_NAME "mma9551_event"
|
||||
#define MMA9551_GPIO_NAME "mma9551_int"
|
||||
#define MMA9551_GPIO_COUNT 4
|
||||
|
||||
/* Tilt application (inclination in IIO terms). */
|
||||
#define MMA9551_TILT_XZ_ANG_REG 0x00
|
||||
#define MMA9551_TILT_YZ_ANG_REG 0x01
|
||||
#define MMA9551_TILT_XY_ANG_REG 0x02
|
||||
#define MMA9551_TILT_ANGFLG BIT(7)
|
||||
#define MMA9551_TILT_QUAD_REG 0x03
|
||||
#define MMA9551_TILT_XY_QUAD_SHIFT 0
|
||||
#define MMA9551_TILT_YZ_QUAD_SHIFT 2
|
||||
#define MMA9551_TILT_XZ_QUAD_SHIFT 4
|
||||
#define MMA9551_TILT_CFG_REG 0x01
|
||||
#define MMA9551_TILT_ANG_THRESH_MASK GENMASK(3, 0)
|
||||
|
||||
#define MMA9551_DEFAULT_SAMPLE_RATE 122 /* Hz */
|
||||
|
||||
/* Tilt events are mapped to the first three GPIO pins. */
|
||||
enum mma9551_tilt_axis {
|
||||
mma9551_x = 0,
|
||||
mma9551_y,
|
||||
mma9551_z,
|
||||
};
|
||||
|
||||
struct mma9551_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex mutex;
|
||||
int event_enabled[3];
|
||||
int irqs[MMA9551_GPIO_COUNT];
|
||||
};
|
||||
|
||||
static int mma9551_read_incli_chan(struct i2c_client *client,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val)
|
||||
{
|
||||
u8 quad_shift, angle, quadrant;
|
||||
u16 reg_addr;
|
||||
int ret;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg_addr = MMA9551_TILT_YZ_ANG_REG;
|
||||
quad_shift = MMA9551_TILT_YZ_QUAD_SHIFT;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg_addr = MMA9551_TILT_XZ_ANG_REG;
|
||||
quad_shift = MMA9551_TILT_XZ_QUAD_SHIFT;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg_addr = MMA9551_TILT_XY_ANG_REG;
|
||||
quad_shift = MMA9551_TILT_XY_QUAD_SHIFT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = mma9551_set_power_state(client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT,
|
||||
reg_addr, &angle);
|
||||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
ret = mma9551_read_status_byte(client, MMA9551_APPID_TILT,
|
||||
MMA9551_TILT_QUAD_REG, &quadrant);
|
||||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
angle &= ~MMA9551_TILT_ANGFLG;
|
||||
quadrant = (quadrant >> quad_shift) & 0x03;
|
||||
|
||||
if (quadrant == 1 || quadrant == 3)
|
||||
*val = 90 * (quadrant + 1) - angle;
|
||||
else
|
||||
*val = angle + 90 * quadrant;
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
|
||||
out_poweroff:
|
||||
mma9551_set_power_state(client, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mma9551_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
switch (chan->type) {
|
||||
case IIO_INCLI:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_read_incli_chan(data->client, chan, val);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
switch (chan->type) {
|
||||
case IIO_ACCEL:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_read_accel_chan(data->client,
|
||||
chan, val, val2);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_ACCEL:
|
||||
return mma9551_read_accel_scale(val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mma9551_read_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INCLI:
|
||||
/* IIO counts axes from 1, because IIO_NO_MOD is 0. */
|
||||
return data->event_enabled[chan->channel2 - 1];
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mma9551_config_incli_event(struct iio_dev *indio_dev,
|
||||
enum iio_modifier axis,
|
||||
int state)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
enum mma9551_tilt_axis mma_axis;
|
||||
int ret;
|
||||
|
||||
/* IIO counts axes from 1, because IIO_NO_MOD is 0. */
|
||||
mma_axis = axis - 1;
|
||||
|
||||
if (data->event_enabled[mma_axis] == state)
|
||||
return 0;
|
||||
|
||||
if (state == 0) {
|
||||
ret = mma9551_gpio_config(data->client,
|
||||
(enum mma9551_gpio_pin)mma_axis,
|
||||
MMA9551_APPID_NONE, 0, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mma9551_set_power_state(data->client, false);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
int bitnum;
|
||||
|
||||
/* Bit 7 of each angle register holds the angle flag. */
|
||||
switch (axis) {
|
||||
case IIO_MOD_X:
|
||||
bitnum = 7 + 8 * MMA9551_TILT_YZ_ANG_REG;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
bitnum = 7 + 8 * MMA9551_TILT_XZ_ANG_REG;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
bitnum = 7 + 8 * MMA9551_TILT_XY_ANG_REG;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
ret = mma9551_set_power_state(data->client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mma9551_gpio_config(data->client,
|
||||
(enum mma9551_gpio_pin)mma_axis,
|
||||
MMA9551_APPID_TILT, bitnum, 0);
|
||||
if (ret < 0) {
|
||||
mma9551_set_power_state(data->client, false);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
data->event_enabled[mma_axis] = state;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mma9551_write_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
int state)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INCLI:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_config_incli_event(indio_dev,
|
||||
chan->channel2, state);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mma9551_write_event_value(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
enum iio_event_info info,
|
||||
int val, int val2)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INCLI:
|
||||
if (val2 != 0 || val < 1 || val > 10)
|
||||
return -EINVAL;
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_update_config_bits(data->client,
|
||||
MMA9551_APPID_TILT,
|
||||
MMA9551_TILT_CFG_REG,
|
||||
MMA9551_TILT_ANG_THRESH_MASK,
|
||||
val);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int mma9551_read_event_value(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
enum iio_event_info info,
|
||||
int *val, int *val2)
|
||||
{
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
u8 tmp;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_INCLI:
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_read_config_byte(data->client,
|
||||
MMA9551_APPID_TILT,
|
||||
MMA9551_TILT_CFG_REG, &tmp);
|
||||
mutex_unlock(&data->mutex);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = tmp & MMA9551_TILT_ANG_THRESH_MASK;
|
||||
*val2 = 0;
|
||||
return IIO_VAL_INT;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct iio_event_spec mma9551_incli_event = {
|
||||
.type = IIO_EV_TYPE_ROC,
|
||||
.dir = IIO_EV_DIR_RISING,
|
||||
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
|
||||
.mask_shared_by_type = BIT(IIO_EV_INFO_VALUE),
|
||||
};
|
||||
|
||||
#define MMA9551_INCLI_CHANNEL(axis) { \
|
||||
.type = IIO_INCLI, \
|
||||
.modified = 1, \
|
||||
.channel2 = axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
|
||||
.event_spec = &mma9551_incli_event, \
|
||||
.num_event_specs = 1, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec mma9551_channels[] = {
|
||||
MMA9551_ACCEL_CHANNEL(IIO_MOD_X),
|
||||
MMA9551_ACCEL_CHANNEL(IIO_MOD_Y),
|
||||
MMA9551_ACCEL_CHANNEL(IIO_MOD_Z),
|
||||
|
||||
MMA9551_INCLI_CHANNEL(IIO_MOD_X),
|
||||
MMA9551_INCLI_CHANNEL(IIO_MOD_Y),
|
||||
MMA9551_INCLI_CHANNEL(IIO_MOD_Z),
|
||||
};
|
||||
|
||||
static const struct iio_info mma9551_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = mma9551_read_raw,
|
||||
.read_event_config = mma9551_read_event_config,
|
||||
.write_event_config = mma9551_write_event_config,
|
||||
.read_event_value = mma9551_read_event_value,
|
||||
.write_event_value = mma9551_write_event_value,
|
||||
};
|
||||
|
||||
static irqreturn_t mma9551_event_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int i, ret, mma_axis = -1;
|
||||
u16 reg;
|
||||
u8 val;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
if (irq == data->irqs[i]) {
|
||||
mma_axis = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mma_axis == -1) {
|
||||
/* IRQ was triggered on 4th line, which we don't use. */
|
||||
dev_warn(&data->client->dev,
|
||||
"irq triggered on unused line %d\n", data->irqs[3]);
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (mma_axis) {
|
||||
case mma9551_x:
|
||||
reg = MMA9551_TILT_YZ_ANG_REG;
|
||||
break;
|
||||
case mma9551_y:
|
||||
reg = MMA9551_TILT_XZ_ANG_REG;
|
||||
break;
|
||||
case mma9551_z:
|
||||
reg = MMA9551_TILT_XY_ANG_REG;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the angle even though we don't use it, otherwise we
|
||||
* won't get any further interrupts.
|
||||
*/
|
||||
ret = mma9551_read_status_byte(data->client, MMA9551_APPID_TILT,
|
||||
reg, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"error %d reading tilt register in IRQ\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
iio_push_event(indio_dev,
|
||||
IIO_MOD_EVENT_CODE(IIO_INCLI, 0, (mma_axis + 1),
|
||||
IIO_EV_TYPE_ROC, IIO_EV_DIR_RISING),
|
||||
iio_get_time_ns());
|
||||
|
||||
out:
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int mma9551_init(struct mma9551_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = mma9551_read_version(data->client);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mma9551_set_device_state(data->client, true);
|
||||
}
|
||||
|
||||
static int mma9551_gpio_probe(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct gpio_desc *gpio;
|
||||
int i, ret;
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
struct device *dev = &data->client->dev;
|
||||
|
||||
for (i = 0; i < MMA9551_GPIO_COUNT; i++) {
|
||||
gpio = devm_gpiod_get_index(dev, MMA9551_GPIO_NAME, i);
|
||||
if (IS_ERR(gpio)) {
|
||||
dev_err(dev, "acpi gpio get index failed\n");
|
||||
return PTR_ERR(gpio);
|
||||
}
|
||||
|
||||
ret = gpiod_direction_input(gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
data->irqs[i] = gpiod_to_irq(gpio);
|
||||
ret = devm_request_threaded_irq(dev, data->irqs[i],
|
||||
NULL, mma9551_event_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
MMA9551_IRQ_NAME, indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "request irq %d failed\n", data->irqs[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "gpio resource, no:%d irq:%d\n",
|
||||
desc_to_gpio(gpio), data->irqs[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *mma9551_match_acpi_device(struct device *dev)
|
||||
{
|
||||
const struct acpi_device_id *id;
|
||||
|
||||
id = acpi_match_device(dev->driver->acpi_match_table, dev);
|
||||
if (!id)
|
||||
return NULL;
|
||||
|
||||
return dev_name(dev);
|
||||
}
|
||||
|
||||
static int mma9551_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct mma9551_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
const char *name = NULL;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
|
||||
if (id)
|
||||
name = id->name;
|
||||
else if (ACPI_HANDLE(&client->dev))
|
||||
name = mma9551_match_acpi_device(&client->dev);
|
||||
|
||||
ret = mma9551_init(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mutex_init(&data->mutex);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = mma9551_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(mma9551_channels);
|
||||
indio_dev->name = name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->info = &mma9551_info;
|
||||
|
||||
ret = mma9551_gpio_probe(indio_dev);
|
||||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "unable to register iio device\n");
|
||||
goto out_poweroff;
|
||||
}
|
||||
|
||||
ret = pm_runtime_set_active(&client->dev);
|
||||
if (ret < 0)
|
||||
goto out_iio_unregister;
|
||||
|
||||
pm_runtime_enable(&client->dev);
|
||||
pm_runtime_set_autosuspend_delay(&client->dev,
|
||||
MMA9551_AUTO_SUSPEND_DELAY_MS);
|
||||
pm_runtime_use_autosuspend(&client->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
out_iio_unregister:
|
||||
iio_device_unregister(indio_dev);
|
||||
out_poweroff:
|
||||
mma9551_set_device_state(client, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mma9551_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
|
||||
pm_runtime_disable(&client->dev);
|
||||
pm_runtime_set_suspended(&client->dev);
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
mutex_lock(&data->mutex);
|
||||
mma9551_set_device_state(data->client, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mma9551_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_set_device_state(data->client, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "powering off device failed\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mma9551_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = mma9551_set_device_state(data->client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mma9551_sleep(MMA9551_DEFAULT_SAMPLE_RATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int mma9551_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_set_device_state(data->client, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mma9551_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct mma9551_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = mma9551_set_device_state(data->client, true);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops mma9551_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(mma9551_suspend, mma9551_resume)
|
||||
SET_RUNTIME_PM_OPS(mma9551_runtime_suspend,
|
||||
mma9551_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
static const struct acpi_device_id mma9551_acpi_match[] = {
|
||||
{"MMA9551", 0},
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(acpi, mma9551_acpi_match);
|
||||
|
||||
static const struct i2c_device_id mma9551_id[] = {
|
||||
{"mma9551", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, mma9551_id);
|
||||
|
||||
static struct i2c_driver mma9551_driver = {
|
||||
.driver = {
|
||||
.name = MMA9551_DRV_NAME,
|
||||
.acpi_match_table = ACPI_PTR(mma9551_acpi_match),
|
||||
.pm = &mma9551_pm_ops,
|
||||
},
|
||||
.probe = mma9551_probe,
|
||||
.remove = mma9551_remove,
|
||||
.id_table = mma9551_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(mma9551_driver);
|
||||
|
||||
MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
|
||||
MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MMA9551L motion-sensing platform driver");
|
|
@ -0,0 +1,798 @@
|
|||
/*
|
||||
* Common code for Freescale MMA955x Intelligent Sensor Platform drivers
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include "mma9551_core.h"
|
||||
|
||||
/* Command masks for mailbox write command */
|
||||
#define MMA9551_CMD_READ_VERSION_INFO 0x00
|
||||
#define MMA9551_CMD_READ_CONFIG 0x10
|
||||
#define MMA9551_CMD_WRITE_CONFIG 0x20
|
||||
#define MMA9551_CMD_READ_STATUS 0x30
|
||||
|
||||
/* Mailbox read command */
|
||||
#define MMA9551_RESPONSE_COCO BIT(7)
|
||||
|
||||
/* Error-Status codes returned in mailbox read command */
|
||||
#define MMA9551_MCI_ERROR_NONE 0x00
|
||||
#define MMA9551_MCI_ERROR_PARAM 0x04
|
||||
#define MMA9551_MCI_INVALID_COUNT 0x19
|
||||
#define MMA9551_MCI_ERROR_COMMAND 0x1C
|
||||
#define MMA9551_MCI_ERROR_INVALID_LENGTH 0x21
|
||||
#define MMA9551_MCI_ERROR_FIFO_BUSY 0x22
|
||||
#define MMA9551_MCI_ERROR_FIFO_ALLOCATED 0x23
|
||||
#define MMA9551_MCI_ERROR_FIFO_OVERSIZE 0x24
|
||||
|
||||
/* GPIO Application */
|
||||
#define MMA9551_GPIO_POL_MSB 0x08
|
||||
#define MMA9551_GPIO_POL_LSB 0x09
|
||||
|
||||
/* Sleep/Wake application */
|
||||
#define MMA9551_SLEEP_CFG 0x06
|
||||
#define MMA9551_SLEEP_CFG_SNCEN BIT(0)
|
||||
#define MMA9551_SLEEP_CFG_FLEEN BIT(1)
|
||||
#define MMA9551_SLEEP_CFG_SCHEN BIT(2)
|
||||
|
||||
/* AFE application */
|
||||
#define MMA9551_AFE_X_ACCEL_REG 0x00
|
||||
#define MMA9551_AFE_Y_ACCEL_REG 0x02
|
||||
#define MMA9551_AFE_Z_ACCEL_REG 0x04
|
||||
|
||||
/* Reset/Suspend/Clear application */
|
||||
#define MMA9551_RSC_RESET 0x00
|
||||
#define MMA9551_RSC_OFFSET(mask) (3 - (ffs(mask) - 1) / 8)
|
||||
#define MMA9551_RSC_VAL(mask) (mask >> (((ffs(mask) - 1) / 8) * 8))
|
||||
|
||||
/*
|
||||
* A response is composed of:
|
||||
* - control registers: MB0-3
|
||||
* - data registers: MB4-31
|
||||
*
|
||||
* A request is composed of:
|
||||
* - mbox to write to (always 0)
|
||||
* - control registers: MB1-4
|
||||
* - data registers: MB5-31
|
||||
*/
|
||||
#define MMA9551_MAILBOX_CTRL_REGS 4
|
||||
#define MMA9551_MAX_MAILBOX_DATA_REGS 28
|
||||
#define MMA9551_MAILBOX_REGS 32
|
||||
|
||||
#define MMA9551_I2C_READ_RETRIES 5
|
||||
#define MMA9551_I2C_READ_DELAY 50 /* us */
|
||||
|
||||
struct mma9551_mbox_request {
|
||||
u8 start_mbox; /* Always 0. */
|
||||
u8 app_id;
|
||||
/*
|
||||
* See Section 5.3.1 of the MMA955xL Software Reference Manual.
|
||||
*
|
||||
* Bit 7: reserved, always 0
|
||||
* Bits 6-4: command
|
||||
* Bits 3-0: upper bits of register offset
|
||||
*/
|
||||
u8 cmd_off;
|
||||
u8 lower_off;
|
||||
u8 nbytes;
|
||||
u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS - 1];
|
||||
} __packed;
|
||||
|
||||
struct mma9551_mbox_response {
|
||||
u8 app_id;
|
||||
/*
|
||||
* See Section 5.3.3 of the MMA955xL Software Reference Manual.
|
||||
*
|
||||
* Bit 7: COCO
|
||||
* Bits 6-0: Error code.
|
||||
*/
|
||||
u8 coco_err;
|
||||
u8 nbytes;
|
||||
u8 req_bytes;
|
||||
u8 buf[MMA9551_MAX_MAILBOX_DATA_REGS];
|
||||
} __packed;
|
||||
|
||||
struct mma9551_version_info {
|
||||
__be32 device_id;
|
||||
u8 rom_version[2];
|
||||
u8 fw_version[2];
|
||||
u8 hw_version[2];
|
||||
u8 fw_build[2];
|
||||
};
|
||||
|
||||
static int mma9551_transfer(struct i2c_client *client,
|
||||
u8 app_id, u8 command, u16 offset,
|
||||
u8 *inbytes, int num_inbytes,
|
||||
u8 *outbytes, int num_outbytes)
|
||||
{
|
||||
struct mma9551_mbox_request req;
|
||||
struct mma9551_mbox_response rsp;
|
||||
struct i2c_msg in, out;
|
||||
u8 req_len, err_code;
|
||||
int ret, retries;
|
||||
|
||||
if (offset >= 1 << 12) {
|
||||
dev_err(&client->dev, "register offset too large\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
req_len = 1 + MMA9551_MAILBOX_CTRL_REGS + num_inbytes;
|
||||
req.start_mbox = 0;
|
||||
req.app_id = app_id;
|
||||
req.cmd_off = command | (offset >> 8);
|
||||
req.lower_off = offset;
|
||||
|
||||
if (command == MMA9551_CMD_WRITE_CONFIG)
|
||||
req.nbytes = num_inbytes;
|
||||
else
|
||||
req.nbytes = num_outbytes;
|
||||
if (num_inbytes)
|
||||
memcpy(req.buf, inbytes, num_inbytes);
|
||||
|
||||
out.addr = client->addr;
|
||||
out.flags = 0;
|
||||
out.len = req_len;
|
||||
out.buf = (u8 *)&req;
|
||||
|
||||
ret = i2c_transfer(client->adapter, &out, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "i2c write failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
retries = MMA9551_I2C_READ_RETRIES;
|
||||
do {
|
||||
udelay(MMA9551_I2C_READ_DELAY);
|
||||
|
||||
in.addr = client->addr;
|
||||
in.flags = I2C_M_RD;
|
||||
in.len = sizeof(rsp);
|
||||
in.buf = (u8 *)&rsp;
|
||||
|
||||
ret = i2c_transfer(client->adapter, &in, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "i2c read failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (rsp.coco_err & MMA9551_RESPONSE_COCO)
|
||||
break;
|
||||
} while (--retries > 0);
|
||||
|
||||
if (retries == 0) {
|
||||
dev_err(&client->dev,
|
||||
"timed out while waiting for command response\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (rsp.app_id != app_id) {
|
||||
dev_err(&client->dev,
|
||||
"app_id mismatch in response got %02x expected %02x\n",
|
||||
rsp.app_id, app_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err_code = rsp.coco_err & ~MMA9551_RESPONSE_COCO;
|
||||
if (err_code != MMA9551_MCI_ERROR_NONE) {
|
||||
dev_err(&client->dev, "read returned error %x\n", err_code);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rsp.nbytes != rsp.req_bytes) {
|
||||
dev_err(&client->dev,
|
||||
"output length mismatch got %d expected %d\n",
|
||||
rsp.nbytes, rsp.req_bytes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (num_outbytes)
|
||||
memcpy(outbytes, rsp.buf, num_outbytes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mma9551_read_config_byte() - read 1 configuration byte
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Pointer to store value read
|
||||
*
|
||||
* Read one configuration byte from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed
|
||||
* by one or more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_config_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 *val)
|
||||
{
|
||||
return mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
|
||||
reg, NULL, 0, val, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_config_byte);
|
||||
|
||||
/**
|
||||
* mma9551_write_config_byte() - write 1 configuration byte
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Value to write
|
||||
*
|
||||
* Write one configuration byte from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed by one or
|
||||
* more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_write_config_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 val)
|
||||
{
|
||||
return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg,
|
||||
&val, 1, NULL, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_write_config_byte);
|
||||
|
||||
/**
|
||||
* mma9551_read_status_byte() - read 1 status byte
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Pointer to store value read
|
||||
*
|
||||
* Read one status byte from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed by one or
|
||||
* more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_status_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 *val)
|
||||
{
|
||||
return mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
|
||||
reg, NULL, 0, val, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_status_byte);
|
||||
|
||||
/**
|
||||
* mma9551_read_config_word() - read 1 config word
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Pointer to store value read
|
||||
*
|
||||
* Read one configuration word from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed by one or
|
||||
* more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_config_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 *val)
|
||||
{
|
||||
int ret;
|
||||
__be16 v;
|
||||
|
||||
ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
|
||||
reg, NULL, 0, (u8 *)&v, 2);
|
||||
*val = be16_to_cpu(v);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_config_word);
|
||||
|
||||
/**
|
||||
* mma9551_write_config_word() - write 1 config word
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Value to write
|
||||
*
|
||||
* Write one configuration word from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed by one or
|
||||
* more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_write_config_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 val)
|
||||
{
|
||||
__be16 v = cpu_to_be16(val);
|
||||
|
||||
return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG, reg,
|
||||
(u8 *) &v, 2, NULL, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_write_config_word);
|
||||
|
||||
/**
|
||||
* mma9551_read_status_word() - read 1 status word
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @val: Pointer to store value read
|
||||
*
|
||||
* Read one status word from the device using MMA955xL command format.
|
||||
* Commands to the MMA955xL platform consist of a write followed by one or
|
||||
* more reads.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_status_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 *val)
|
||||
{
|
||||
int ret;
|
||||
__be16 v;
|
||||
|
||||
ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
|
||||
reg, NULL, 0, (u8 *)&v, 2);
|
||||
*val = be16_to_cpu(v);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_status_word);
|
||||
|
||||
/**
|
||||
* mma9551_read_config_words() - read multiple config words
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @len: Length of array to read in bytes
|
||||
* @val: Array of words to read
|
||||
*
|
||||
* Read multiple configuration registers (word-sized registers).
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_config_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf)
|
||||
{
|
||||
int ret, i;
|
||||
int len_words = len / sizeof(u16);
|
||||
__be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS];
|
||||
|
||||
ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_CONFIG,
|
||||
reg, NULL, 0, (u8 *) be_buf, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < len_words; i++)
|
||||
buf[i] = be16_to_cpu(be_buf[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_config_words);
|
||||
|
||||
/**
|
||||
* mma9551_read_status_words() - read multiple status words
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @len: Length of array to read in bytes
|
||||
* @val: Array of words to read
|
||||
*
|
||||
* Read multiple status registers (word-sized registers).
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_status_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf)
|
||||
{
|
||||
int ret, i;
|
||||
int len_words = len / sizeof(u16);
|
||||
__be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS];
|
||||
|
||||
ret = mma9551_transfer(client, app_id, MMA9551_CMD_READ_STATUS,
|
||||
reg, NULL, 0, (u8 *) be_buf, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < len_words; i++)
|
||||
buf[i] = be16_to_cpu(be_buf[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_status_words);
|
||||
|
||||
/**
|
||||
* mma9551_write_config_words() - write multiple config words
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @len: Length of array to write in bytes
|
||||
* @val: Array of words to write
|
||||
*
|
||||
* Write multiple configuration registers (word-sized registers).
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_write_config_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf)
|
||||
{
|
||||
int i;
|
||||
int len_words = len / sizeof(u16);
|
||||
__be16 be_buf[MMA9551_MAX_MAILBOX_DATA_REGS];
|
||||
|
||||
for (i = 0; i < len_words; i++)
|
||||
be_buf[i] = cpu_to_be16(buf[i]);
|
||||
|
||||
return mma9551_transfer(client, app_id, MMA9551_CMD_WRITE_CONFIG,
|
||||
reg, (u8 *) be_buf, len, NULL, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_write_config_words);
|
||||
|
||||
/**
|
||||
* mma9551_update_config_bits() - update bits in register
|
||||
* @client: I2C client
|
||||
* @app_id: Application ID
|
||||
* @reg: Application register
|
||||
* @mask: Mask for the bits to update
|
||||
* @val: Value of the bits to update
|
||||
*
|
||||
* Update bits in the given register using a bit mask.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_update_config_bits(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 mask, u8 val)
|
||||
{
|
||||
int ret;
|
||||
u8 tmp, orig;
|
||||
|
||||
ret = mma9551_read_config_byte(client, app_id, reg, &orig);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
tmp = orig & ~mask;
|
||||
tmp |= val & mask;
|
||||
|
||||
if (tmp == orig)
|
||||
return 0;
|
||||
|
||||
return mma9551_write_config_byte(client, app_id, reg, tmp);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_update_config_bits);
|
||||
|
||||
/**
|
||||
* mma9551_gpio_config() - configure gpio
|
||||
* @client: I2C client
|
||||
* @pin: GPIO pin to configure
|
||||
* @app_id: Application ID
|
||||
* @bitnum: Bit number of status register being assigned to the GPIO pin.
|
||||
* @polarity: The polarity parameter is described in section 6.2.2, page 66,
|
||||
* of the Software Reference Manual. Basically, polarity=0 means
|
||||
* the interrupt line has the same value as the selected bit,
|
||||
* while polarity=1 means the line is inverted.
|
||||
*
|
||||
* Assign a bit from an application’s status register to a specific GPIO pin.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin,
|
||||
u8 app_id, u8 bitnum, int polarity)
|
||||
{
|
||||
u8 reg, pol_mask, pol_val;
|
||||
int ret;
|
||||
|
||||
if (pin > mma9551_gpio_max) {
|
||||
dev_err(&client->dev, "bad GPIO pin\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pin 6 is configured by regs 0x00 and 0x01, pin 7 by 0x02 and
|
||||
* 0x03, and so on.
|
||||
*/
|
||||
reg = pin * 2;
|
||||
|
||||
ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO,
|
||||
reg, app_id);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "error setting GPIO app_id\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = mma9551_write_config_byte(client, MMA9551_APPID_GPIO,
|
||||
reg + 1, bitnum);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "error setting GPIO bit number\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (pin) {
|
||||
case mma9551_gpio6:
|
||||
reg = MMA9551_GPIO_POL_LSB;
|
||||
pol_mask = 1 << 6;
|
||||
break;
|
||||
case mma9551_gpio7:
|
||||
reg = MMA9551_GPIO_POL_LSB;
|
||||
pol_mask = 1 << 7;
|
||||
break;
|
||||
case mma9551_gpio8:
|
||||
reg = MMA9551_GPIO_POL_MSB;
|
||||
pol_mask = 1 << 0;
|
||||
break;
|
||||
case mma9551_gpio9:
|
||||
reg = MMA9551_GPIO_POL_MSB;
|
||||
pol_mask = 1 << 1;
|
||||
break;
|
||||
}
|
||||
pol_val = polarity ? pol_mask : 0;
|
||||
|
||||
ret = mma9551_update_config_bits(client, MMA9551_APPID_GPIO, reg,
|
||||
pol_mask, pol_val);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "error setting GPIO polarity\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_gpio_config);
|
||||
|
||||
/**
|
||||
* mma9551_read_version() - read device version information
|
||||
* @client: I2C client
|
||||
*
|
||||
* Read version information and print device id and firmware version.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_version(struct i2c_client *client)
|
||||
{
|
||||
struct mma9551_version_info info;
|
||||
int ret;
|
||||
|
||||
ret = mma9551_transfer(client, MMA9551_APPID_VERSION, 0x00, 0x00,
|
||||
NULL, 0, (u8 *)&info, sizeof(info));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_info(&client->dev, "device ID 0x%x, firmware version %02x.%02x\n",
|
||||
be32_to_cpu(info.device_id), info.fw_version[0],
|
||||
info.fw_version[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_version);
|
||||
|
||||
/**
|
||||
* mma9551_set_device_state() - sets HW power mode
|
||||
* @client: I2C client
|
||||
* @enable: Use true to power on device, false to cause the device
|
||||
* to enter sleep.
|
||||
*
|
||||
* Set power on/off for device using the Sleep/Wake Application.
|
||||
* When enable is true, power on chip and enable doze mode.
|
||||
* When enable is false, enter sleep mode (device remains in the
|
||||
* lowest-power mode).
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_set_device_state(struct i2c_client *client, bool enable)
|
||||
{
|
||||
return mma9551_update_config_bits(client, MMA9551_APPID_SLEEP_WAKE,
|
||||
MMA9551_SLEEP_CFG,
|
||||
MMA9551_SLEEP_CFG_SNCEN |
|
||||
MMA9551_SLEEP_CFG_FLEEN |
|
||||
MMA9551_SLEEP_CFG_SCHEN,
|
||||
enable ? MMA9551_SLEEP_CFG_SCHEN |
|
||||
MMA9551_SLEEP_CFG_FLEEN :
|
||||
MMA9551_SLEEP_CFG_SNCEN);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_set_device_state);
|
||||
|
||||
/**
|
||||
* mma9551_set_power_state() - sets runtime PM state
|
||||
* @client: I2C client
|
||||
* @on: Use true to power on device, false to power off
|
||||
*
|
||||
* Resume or suspend the device using Runtime PM.
|
||||
* The device will suspend after the autosuspend delay.
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_set_power_state(struct i2c_client *client, bool on)
|
||||
{
|
||||
#ifdef CONFIG_PM
|
||||
int ret;
|
||||
|
||||
if (on)
|
||||
ret = pm_runtime_get_sync(&client->dev);
|
||||
else {
|
||||
pm_runtime_mark_last_busy(&client->dev);
|
||||
ret = pm_runtime_put_autosuspend(&client->dev);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"failed to change power state to %d\n", on);
|
||||
if (on)
|
||||
pm_runtime_put_noidle(&client->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_set_power_state);
|
||||
|
||||
/**
|
||||
* mma9551_sleep() - sleep
|
||||
* @freq: Application frequency
|
||||
*
|
||||
* Firmware applications run at a certain frequency on the
|
||||
* device. Sleep for one application cycle to make sure the
|
||||
* application had time to run once and initialize set values.
|
||||
*/
|
||||
void mma9551_sleep(int freq)
|
||||
{
|
||||
int sleep_val = 1000 / freq;
|
||||
|
||||
if (sleep_val < 20)
|
||||
usleep_range(sleep_val * 1000, 20000);
|
||||
else
|
||||
msleep_interruptible(sleep_val);
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_sleep);
|
||||
|
||||
/**
|
||||
* mma9551_read_accel_chan() - read accelerometer channel
|
||||
* @client: I2C client
|
||||
* @chan: IIO channel
|
||||
* @val: Pointer to the accelerometer value read
|
||||
* @val2: Unused
|
||||
*
|
||||
* Read accelerometer value for the specified channel.
|
||||
*
|
||||
* Locking note: This function must be called with the device lock held.
|
||||
* Locking is not handled inside the function. Callers should ensure they
|
||||
* serialize access to the HW.
|
||||
*
|
||||
* Returns: IIO_VAL_INT on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_read_accel_chan(struct i2c_client *client,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2)
|
||||
{
|
||||
u16 reg_addr;
|
||||
s16 raw_accel;
|
||||
int ret;
|
||||
|
||||
switch (chan->channel2) {
|
||||
case IIO_MOD_X:
|
||||
reg_addr = MMA9551_AFE_X_ACCEL_REG;
|
||||
break;
|
||||
case IIO_MOD_Y:
|
||||
reg_addr = MMA9551_AFE_Y_ACCEL_REG;
|
||||
break;
|
||||
case IIO_MOD_Z:
|
||||
reg_addr = MMA9551_AFE_Z_ACCEL_REG;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = mma9551_set_power_state(client, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = mma9551_read_status_word(client, MMA9551_APPID_AFE,
|
||||
reg_addr, &raw_accel);
|
||||
if (ret < 0)
|
||||
goto out_poweroff;
|
||||
|
||||
*val = raw_accel;
|
||||
|
||||
ret = IIO_VAL_INT;
|
||||
|
||||
out_poweroff:
|
||||
mma9551_set_power_state(client, false);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_accel_chan);
|
||||
|
||||
/**
|
||||
* mma9551_read_accel_scale() - read accelerometer scale
|
||||
* @val: Pointer to the accelerometer scale (int value)
|
||||
* @val2: Pointer to the accelerometer scale (micro value)
|
||||
*
|
||||
* Read accelerometer scale.
|
||||
*
|
||||
* Returns: IIO_VAL_INT_PLUS_MICRO.
|
||||
*/
|
||||
int mma9551_read_accel_scale(int *val, int *val2)
|
||||
{
|
||||
*val = 0;
|
||||
*val2 = 2440;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_read_accel_scale);
|
||||
|
||||
/**
|
||||
* mma9551_app_reset() - reset application
|
||||
* @client: I2C client
|
||||
* @app_mask: Application to reset
|
||||
*
|
||||
* Reset the given application (using the Reset/Suspend/Clear
|
||||
* Control Application)
|
||||
*
|
||||
* Returns: 0 on success, negative value on failure.
|
||||
*/
|
||||
int mma9551_app_reset(struct i2c_client *client, u32 app_mask)
|
||||
{
|
||||
return mma9551_write_config_byte(client, MMA9551_APPID_RCS,
|
||||
MMA9551_RSC_RESET +
|
||||
MMA9551_RSC_OFFSET(app_mask),
|
||||
MMA9551_RSC_VAL(app_mask));
|
||||
}
|
||||
EXPORT_SYMBOL(mma9551_app_reset);
|
||||
|
||||
MODULE_AUTHOR("Irina Tirdea <irina.tirdea@intel.com>");
|
||||
MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MMA955xL sensors core");
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Common code for Freescale MMA955x Intelligent Sensor Platform drivers
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#ifndef _MMA9551_CORE_H_
|
||||
#define _MMA9551_CORE_H_
|
||||
|
||||
/* Applications IDs */
|
||||
#define MMA9551_APPID_VERSION 0x00
|
||||
#define MMA9551_APPID_GPIO 0x03
|
||||
#define MMA9551_APPID_AFE 0x06
|
||||
#define MMA9551_APPID_TILT 0x0B
|
||||
#define MMA9551_APPID_SLEEP_WAKE 0x12
|
||||
#define MMA9551_APPID_PEDOMETER 0x15
|
||||
#define MMA9551_APPID_RCS 0x17
|
||||
#define MMA9551_APPID_NONE 0xff
|
||||
|
||||
/* Reset/Suspend/Clear application app masks */
|
||||
#define MMA9551_RSC_PED BIT(21)
|
||||
|
||||
#define MMA9551_AUTO_SUSPEND_DELAY_MS 2000
|
||||
|
||||
enum mma9551_gpio_pin {
|
||||
mma9551_gpio6 = 0,
|
||||
mma9551_gpio7,
|
||||
mma9551_gpio8,
|
||||
mma9551_gpio9,
|
||||
mma9551_gpio_max = mma9551_gpio9,
|
||||
};
|
||||
|
||||
#define MMA9551_ACCEL_CHANNEL(axis) { \
|
||||
.type = IIO_ACCEL, \
|
||||
.modified = 1, \
|
||||
.channel2 = axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
|
||||
}
|
||||
|
||||
int mma9551_read_config_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 *val);
|
||||
int mma9551_write_config_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 val);
|
||||
int mma9551_read_status_byte(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 *val);
|
||||
int mma9551_read_config_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 *val);
|
||||
int mma9551_write_config_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 val);
|
||||
int mma9551_read_status_word(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u16 *val);
|
||||
int mma9551_read_config_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf);
|
||||
int mma9551_read_status_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf);
|
||||
int mma9551_write_config_words(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 len, u16 *buf);
|
||||
int mma9551_update_config_bits(struct i2c_client *client, u8 app_id,
|
||||
u16 reg, u8 mask, u8 val);
|
||||
int mma9551_gpio_config(struct i2c_client *client, enum mma9551_gpio_pin pin,
|
||||
u8 app_id, u8 bitnum, int polarity);
|
||||
int mma9551_read_version(struct i2c_client *client);
|
||||
int mma9551_set_device_state(struct i2c_client *client, bool enable);
|
||||
int mma9551_set_power_state(struct i2c_client *client, bool on);
|
||||
void mma9551_sleep(int freq);
|
||||
int mma9551_read_accel_chan(struct i2c_client *client,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2);
|
||||
int mma9551_read_accel_scale(int *val, int *val2);
|
||||
int mma9551_app_reset(struct i2c_client *client, u32 app_mask);
|
||||
|
||||
#endif /* _MMA9551_CORE_H_ */
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/iio/common/ssp_sensors.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../common/ssp_sensors/ssp_iio_sensor.h"
|
||||
|
||||
#define SSP_CHANNEL_COUNT 3
|
||||
|
||||
#define SSP_ACCEL_NAME "ssp-accelerometer"
|
||||
static const char ssp_accel_device_name[] = SSP_ACCEL_NAME;
|
||||
|
||||
enum ssp_accel_3d_channel {
|
||||
SSP_CHANNEL_SCAN_INDEX_X,
|
||||
SSP_CHANNEL_SCAN_INDEX_Y,
|
||||
SSP_CHANNEL_SCAN_INDEX_Z,
|
||||
SSP_CHANNEL_SCAN_INDEX_TIME,
|
||||
};
|
||||
|
||||
static int ssp_accel_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
u32 t;
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
t = ssp_get_sensor_delay(data, SSP_ACCELEROMETER_SENSOR);
|
||||
ssp_convert_to_freq(t, val, val2);
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ssp_accel_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val,
|
||||
int val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = ssp_convert_to_time(val, val2);
|
||||
ret = ssp_change_delay(data, SSP_ACCELEROMETER_SENSOR, ret);
|
||||
if (ret < 0)
|
||||
dev_err(&indio_dev->dev, "accel sensor enable fail\n");
|
||||
|
||||
return ret;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct iio_info ssp_accel_iio_info = {
|
||||
.read_raw = &ssp_accel_read_raw,
|
||||
.write_raw = &ssp_accel_write_raw,
|
||||
};
|
||||
|
||||
static const unsigned long ssp_accel_scan_mask[] = { 0x7, 0, };
|
||||
|
||||
static const struct iio_chan_spec ssp_acc_channels[] = {
|
||||
SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X),
|
||||
SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y),
|
||||
SSP_CHANNEL_AG(IIO_ACCEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z),
|
||||
SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME),
|
||||
};
|
||||
|
||||
static int ssp_process_accel_data(struct iio_dev *indio_dev, void *buf,
|
||||
int64_t timestamp)
|
||||
{
|
||||
return ssp_common_process_data(indio_dev, buf, SSP_ACCELEROMETER_SIZE,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops ssp_accel_buffer_ops = {
|
||||
.postenable = &ssp_common_buffer_postenable,
|
||||
.postdisable = &ssp_common_buffer_postdisable,
|
||||
};
|
||||
|
||||
static int ssp_accel_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct ssp_sensor_data *spd;
|
||||
struct iio_buffer *buffer;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
spd = iio_priv(indio_dev);
|
||||
|
||||
spd->process_data = ssp_process_accel_data;
|
||||
spd->type = SSP_ACCELEROMETER_SENSOR;
|
||||
|
||||
indio_dev->name = ssp_accel_device_name;
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->dev.of_node = pdev->dev.of_node;
|
||||
indio_dev->info = &ssp_accel_iio_info;
|
||||
indio_dev->modes = INDIO_BUFFER_SOFTWARE;
|
||||
indio_dev->channels = ssp_acc_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ssp_acc_channels);
|
||||
indio_dev->available_scan_masks = ssp_accel_scan_mask;
|
||||
|
||||
buffer = devm_iio_kfifo_allocate(&pdev->dev);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
iio_device_attach_buffer(indio_dev, buffer);
|
||||
|
||||
indio_dev->setup_ops = &ssp_accel_buffer_ops;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* ssp registering should be done after all iio setup */
|
||||
ssp_register_consumer(indio_dev, SSP_ACCELEROMETER_SENSOR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_accel_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ssp_accel_driver = {
|
||||
.driver = {
|
||||
.name = SSP_ACCEL_NAME,
|
||||
},
|
||||
.probe = ssp_accel_probe,
|
||||
.remove = ssp_accel_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ssp_accel_driver);
|
||||
|
||||
MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>");
|
||||
MODULE_DESCRIPTION("Samsung sensorhub accelerometers driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -135,6 +135,17 @@ config AXP288_ADC
|
|||
device. Depending on platform configuration, this general purpose ADC can
|
||||
be used for sampling sensors such as thermal resistors.
|
||||
|
||||
config CC10001_ADC
|
||||
tristate "Cosmic Circuits 10001 ADC driver"
|
||||
depends on HAS_IOMEM || HAVE_CLK || REGULATOR
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say yes here to build support for Cosmic Circuits 10001 ADC.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called cc10001_adc.
|
||||
|
||||
config EXYNOS_ADC
|
||||
tristate "Exynos ADC driver support"
|
||||
depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST)
|
||||
|
@ -228,6 +239,20 @@ config QCOM_SPMI_IADC
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called qcom-spmi-iadc.
|
||||
|
||||
config QCOM_SPMI_VADC
|
||||
tristate "Qualcomm SPMI PMIC voltage ADC"
|
||||
depends on SPMI
|
||||
select REGMAP_SPMI
|
||||
help
|
||||
This is the IIO Voltage ADC driver for Qualcomm QPNP VADC Chip.
|
||||
|
||||
The driver supports multiple channels read. The VADC is a 15-bit
|
||||
sigma-delta ADC. Some of the channels are internally used for
|
||||
calibration.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called qcom-spmi-vadc.
|
||||
|
||||
config ROCKCHIP_SARADC
|
||||
tristate "Rockchip SARADC driver"
|
||||
depends on ARCH_ROCKCHIP || (ARM && COMPILE_TEST)
|
||||
|
|
|
@ -15,6 +15,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
|
|||
obj-$(CONFIG_AD799X) += ad799x.o
|
||||
obj-$(CONFIG_AT91_ADC) += at91_adc.o
|
||||
obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
|
||||
obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o
|
||||
obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
|
||||
obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
|
||||
obj-$(CONFIG_MAX1027) += max1027.o
|
||||
|
@ -24,6 +25,7 @@ obj-$(CONFIG_MCP3422) += mcp3422.o
|
|||
obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
|
||||
obj-$(CONFIG_NAU7802) += nau7802.o
|
||||
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
|
||||
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
|
||||
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
|
||||
obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
|
||||
obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o
|
||||
|
|
|
@ -0,0 +1,423 @@
|
|||
/*
|
||||
* Copyright (c) 2014-2015 Imagination Technologies Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
|
||||
/* Registers */
|
||||
#define CC10001_ADC_CONFIG 0x00
|
||||
#define CC10001_ADC_START_CONV BIT(4)
|
||||
#define CC10001_ADC_MODE_SINGLE_CONV BIT(5)
|
||||
|
||||
#define CC10001_ADC_DDATA_OUT 0x04
|
||||
#define CC10001_ADC_EOC 0x08
|
||||
#define CC10001_ADC_EOC_SET BIT(0)
|
||||
|
||||
#define CC10001_ADC_CHSEL_SAMPLED 0x0c
|
||||
#define CC10001_ADC_POWER_UP 0x10
|
||||
#define CC10001_ADC_POWER_UP_SET BIT(0)
|
||||
#define CC10001_ADC_DEBUG 0x14
|
||||
#define CC10001_ADC_DATA_COUNT 0x20
|
||||
|
||||
#define CC10001_ADC_DATA_MASK GENMASK(9, 0)
|
||||
#define CC10001_ADC_NUM_CHANNELS 8
|
||||
#define CC10001_ADC_CH_MASK GENMASK(2, 0)
|
||||
|
||||
#define CC10001_INVALID_SAMPLED 0xffff
|
||||
#define CC10001_MAX_POLL_COUNT 20
|
||||
|
||||
/*
|
||||
* As per device specification, wait six clock cycles after power-up to
|
||||
* activate START. Since adding two more clock cycles delay does not
|
||||
* impact the performance too much, we are adding two additional cycles delay
|
||||
* intentionally here.
|
||||
*/
|
||||
#define CC10001_WAIT_CYCLES 8
|
||||
|
||||
struct cc10001_adc_device {
|
||||
void __iomem *reg_base;
|
||||
struct clk *adc_clk;
|
||||
struct regulator *reg;
|
||||
u16 *buf;
|
||||
|
||||
struct mutex lock;
|
||||
unsigned long channel_map;
|
||||
unsigned int start_delay_ns;
|
||||
unsigned int eoc_delay_ns;
|
||||
};
|
||||
|
||||
static inline void cc10001_adc_write_reg(struct cc10001_adc_device *adc_dev,
|
||||
u32 reg, u32 val)
|
||||
{
|
||||
writel(val, adc_dev->reg_base + reg);
|
||||
}
|
||||
|
||||
static inline u32 cc10001_adc_read_reg(struct cc10001_adc_device *adc_dev,
|
||||
u32 reg)
|
||||
{
|
||||
return readl(adc_dev->reg_base + reg);
|
||||
}
|
||||
|
||||
static void cc10001_adc_start(struct cc10001_adc_device *adc_dev,
|
||||
unsigned int channel)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* Channel selection and mode of operation */
|
||||
val = (channel & CC10001_ADC_CH_MASK) | CC10001_ADC_MODE_SINGLE_CONV;
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val);
|
||||
|
||||
val = cc10001_adc_read_reg(adc_dev, CC10001_ADC_CONFIG);
|
||||
val = val | CC10001_ADC_START_CONV;
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_CONFIG, val);
|
||||
}
|
||||
|
||||
static u16 cc10001_adc_poll_done(struct iio_dev *indio_dev,
|
||||
unsigned int channel,
|
||||
unsigned int delay)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
unsigned int poll_count = 0;
|
||||
|
||||
while (!(cc10001_adc_read_reg(adc_dev, CC10001_ADC_EOC) &
|
||||
CC10001_ADC_EOC_SET)) {
|
||||
|
||||
ndelay(delay);
|
||||
if (poll_count++ == CC10001_MAX_POLL_COUNT)
|
||||
return CC10001_INVALID_SAMPLED;
|
||||
}
|
||||
|
||||
poll_count = 0;
|
||||
while ((cc10001_adc_read_reg(adc_dev, CC10001_ADC_CHSEL_SAMPLED) &
|
||||
CC10001_ADC_CH_MASK) != channel) {
|
||||
|
||||
ndelay(delay);
|
||||
if (poll_count++ == CC10001_MAX_POLL_COUNT)
|
||||
return CC10001_INVALID_SAMPLED;
|
||||
}
|
||||
|
||||
/* Read the 10 bit output register */
|
||||
return cc10001_adc_read_reg(adc_dev, CC10001_ADC_DDATA_OUT) &
|
||||
CC10001_ADC_DATA_MASK;
|
||||
}
|
||||
|
||||
static irqreturn_t cc10001_adc_trigger_h(int irq, void *p)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev;
|
||||
struct iio_poll_func *pf = p;
|
||||
struct iio_dev *indio_dev;
|
||||
unsigned int delay_ns;
|
||||
unsigned int channel;
|
||||
bool sample_invalid;
|
||||
u16 *data;
|
||||
int i;
|
||||
|
||||
indio_dev = pf->indio_dev;
|
||||
adc_dev = iio_priv(indio_dev);
|
||||
data = adc_dev->buf;
|
||||
|
||||
mutex_lock(&adc_dev->lock);
|
||||
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP,
|
||||
CC10001_ADC_POWER_UP_SET);
|
||||
|
||||
/* Wait for 8 (6+2) clock cycles before activating START */
|
||||
ndelay(adc_dev->start_delay_ns);
|
||||
|
||||
/* Calculate delay step for eoc and sampled data */
|
||||
delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT;
|
||||
|
||||
i = 0;
|
||||
sample_invalid = false;
|
||||
for_each_set_bit(channel, indio_dev->active_scan_mask,
|
||||
indio_dev->masklength) {
|
||||
|
||||
cc10001_adc_start(adc_dev, channel);
|
||||
|
||||
data[i] = cc10001_adc_poll_done(indio_dev, channel, delay_ns);
|
||||
if (data[i] == CC10001_INVALID_SAMPLED) {
|
||||
dev_warn(&indio_dev->dev,
|
||||
"invalid sample on channel %d\n", channel);
|
||||
sample_invalid = true;
|
||||
goto done;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
done:
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, 0);
|
||||
|
||||
mutex_unlock(&adc_dev->lock);
|
||||
|
||||
if (!sample_invalid)
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data,
|
||||
iio_get_time_ns());
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static u16 cc10001_adc_read_raw_voltage(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
unsigned int delay_ns;
|
||||
u16 val;
|
||||
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP,
|
||||
CC10001_ADC_POWER_UP_SET);
|
||||
|
||||
/* Wait for 8 (6+2) clock cycles before activating START */
|
||||
ndelay(adc_dev->start_delay_ns);
|
||||
|
||||
/* Calculate delay step for eoc and sampled data */
|
||||
delay_ns = adc_dev->eoc_delay_ns / CC10001_MAX_POLL_COUNT;
|
||||
|
||||
cc10001_adc_start(adc_dev, chan->channel);
|
||||
|
||||
val = cc10001_adc_poll_done(indio_dev, chan->channel, delay_ns);
|
||||
|
||||
cc10001_adc_write_reg(adc_dev, CC10001_ADC_POWER_UP, 0);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int cc10001_adc_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
mutex_lock(&adc_dev->lock);
|
||||
*val = cc10001_adc_read_raw_voltage(indio_dev, chan);
|
||||
mutex_unlock(&adc_dev->lock);
|
||||
|
||||
if (*val == CC10001_INVALID_SAMPLED)
|
||||
return -EIO;
|
||||
return IIO_VAL_INT;
|
||||
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
ret = regulator_get_voltage(adc_dev->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = ret / 1000;
|
||||
*val2 = chan->scan_type.realbits;
|
||||
return IIO_VAL_FRACTIONAL_LOG2;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int cc10001_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
|
||||
kfree(adc_dev->buf);
|
||||
adc_dev->buf = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
|
||||
if (!adc_dev->buf)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_info cc10001_adc_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &cc10001_adc_read_raw,
|
||||
.update_scan_mode = &cc10001_update_scan_mode,
|
||||
};
|
||||
|
||||
static int cc10001_adc_channel_init(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
struct iio_chan_spec *chan_array, *timestamp;
|
||||
unsigned int bit, idx = 0;
|
||||
|
||||
indio_dev->num_channels = bitmap_weight(&adc_dev->channel_map,
|
||||
CC10001_ADC_NUM_CHANNELS);
|
||||
|
||||
chan_array = devm_kcalloc(&indio_dev->dev, indio_dev->num_channels + 1,
|
||||
sizeof(struct iio_chan_spec),
|
||||
GFP_KERNEL);
|
||||
if (!chan_array)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_set_bit(bit, &adc_dev->channel_map, CC10001_ADC_NUM_CHANNELS) {
|
||||
struct iio_chan_spec *chan = &chan_array[idx];
|
||||
|
||||
chan->type = IIO_VOLTAGE;
|
||||
chan->indexed = 1;
|
||||
chan->channel = bit;
|
||||
chan->scan_index = idx;
|
||||
chan->scan_type.sign = 'u';
|
||||
chan->scan_type.realbits = 10;
|
||||
chan->scan_type.storagebits = 16;
|
||||
chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
|
||||
chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
|
||||
idx++;
|
||||
}
|
||||
|
||||
timestamp = &chan_array[idx];
|
||||
timestamp->type = IIO_TIMESTAMP;
|
||||
timestamp->channel = -1;
|
||||
timestamp->scan_index = idx;
|
||||
timestamp->scan_type.sign = 's';
|
||||
timestamp->scan_type.realbits = 64;
|
||||
timestamp->scan_type.storagebits = 64;
|
||||
|
||||
indio_dev->channels = chan_array;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cc10001_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct cc10001_adc_device *adc_dev;
|
||||
unsigned long adc_clk_rate;
|
||||
struct resource *res;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc_dev));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
adc_dev = iio_priv(indio_dev);
|
||||
|
||||
adc_dev->channel_map = GENMASK(CC10001_ADC_NUM_CHANNELS - 1, 0);
|
||||
if (!of_property_read_u32(node, "adc-reserved-channels", &ret))
|
||||
adc_dev->channel_map &= ~ret;
|
||||
|
||||
adc_dev->reg = devm_regulator_get(&pdev->dev, "vref");
|
||||
if (IS_ERR(adc_dev->reg))
|
||||
return PTR_ERR(adc_dev->reg);
|
||||
|
||||
ret = regulator_enable(adc_dev->reg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->name = dev_name(&pdev->dev);
|
||||
indio_dev->info = &cc10001_adc_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
adc_dev->reg_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(adc_dev->reg_base)) {
|
||||
ret = PTR_ERR(adc_dev->reg_base);
|
||||
goto err_disable_reg;
|
||||
}
|
||||
|
||||
adc_dev->adc_clk = devm_clk_get(&pdev->dev, "adc");
|
||||
if (IS_ERR(adc_dev->adc_clk)) {
|
||||
dev_err(&pdev->dev, "failed to get the clock\n");
|
||||
ret = PTR_ERR(adc_dev->adc_clk);
|
||||
goto err_disable_reg;
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(adc_dev->adc_clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable the clock\n");
|
||||
goto err_disable_reg;
|
||||
}
|
||||
|
||||
adc_clk_rate = clk_get_rate(adc_dev->adc_clk);
|
||||
if (!adc_clk_rate) {
|
||||
ret = -EINVAL;
|
||||
dev_err(&pdev->dev, "null clock rate!\n");
|
||||
goto err_disable_clk;
|
||||
}
|
||||
|
||||
adc_dev->eoc_delay_ns = NSEC_PER_SEC / adc_clk_rate;
|
||||
adc_dev->start_delay_ns = adc_dev->eoc_delay_ns * CC10001_WAIT_CYCLES;
|
||||
|
||||
/* Setup the ADC channels available on the device */
|
||||
ret = cc10001_adc_channel_init(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_disable_clk;
|
||||
|
||||
mutex_init(&adc_dev->lock);
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
&cc10001_adc_trigger_h, NULL);
|
||||
if (ret < 0)
|
||||
goto err_disable_clk;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto err_cleanup_buffer;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_buffer:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
err_disable_clk:
|
||||
clk_disable_unprepare(adc_dev->adc_clk);
|
||||
err_disable_reg:
|
||||
regulator_disable(adc_dev->reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cc10001_adc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct cc10001_adc_device *adc_dev = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
clk_disable_unprepare(adc_dev->adc_clk);
|
||||
regulator_disable(adc_dev->reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id cc10001_adc_dt_ids[] = {
|
||||
{ .compatible = "cosmic,10001-adc", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cc10001_adc_dt_ids);
|
||||
|
||||
static struct platform_driver cc10001_adc_driver = {
|
||||
.driver = {
|
||||
.name = "cc10001-adc",
|
||||
.of_match_table = cc10001_adc_dt_ids,
|
||||
},
|
||||
.probe = cc10001_adc_probe,
|
||||
.remove = cc10001_adc_remove,
|
||||
};
|
||||
module_platform_driver(cc10001_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Phani Movva <Phani.Movva@imgtec.com>");
|
||||
MODULE_DESCRIPTION("Cosmic Circuits ADC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
File diff suppressed because it is too large
Load Diff
|
@ -249,7 +249,7 @@ static int tiadc_iio_buffered_hardware_setup(struct iio_dev *indio_dev,
|
|||
struct iio_buffer *buffer;
|
||||
int ret;
|
||||
|
||||
buffer = iio_kfifo_allocate(indio_dev);
|
||||
buffer = iio_kfifo_allocate();
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -263,16 +263,8 @@ static int tiadc_iio_buffered_hardware_setup(struct iio_dev *indio_dev,
|
|||
indio_dev->setup_ops = setup_ops;
|
||||
indio_dev->modes |= INDIO_BUFFER_HARDWARE;
|
||||
|
||||
ret = iio_buffer_register(indio_dev,
|
||||
indio_dev->channels,
|
||||
indio_dev->num_channels);
|
||||
if (ret)
|
||||
goto error_free_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_irq:
|
||||
free_irq(irq, indio_dev);
|
||||
error_kfifo_free:
|
||||
iio_kfifo_free(indio_dev->buffer);
|
||||
return ret;
|
||||
|
@ -284,7 +276,6 @@ static void tiadc_iio_buffered_hardware_remove(struct iio_dev *indio_dev)
|
|||
|
||||
free_irq(adc_dev->mfd_tscadc->irq, indio_dev);
|
||||
iio_kfifo_free(indio_dev->buffer);
|
||||
iio_buffer_unregister(indio_dev);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ struct ad8366_state {
|
|||
};
|
||||
|
||||
static int ad8366_write(struct iio_dev *indio_dev,
|
||||
unsigned char ch_a, char unsigned ch_b)
|
||||
unsigned char ch_a, unsigned char ch_b)
|
||||
{
|
||||
struct ad8366_state *st = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
@ -166,7 +166,7 @@ static int ad8366_probe(struct spi_device *spi)
|
|||
if (ret)
|
||||
goto error_disable_reg;
|
||||
|
||||
ad8366_write(indio_dev, 0 , 0);
|
||||
ad8366_write(indio_dev, 0, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
#
|
||||
|
||||
source "drivers/iio/common/hid-sensors/Kconfig"
|
||||
source "drivers/iio/common/ssp_sensors/Kconfig"
|
||||
source "drivers/iio/common/st_sensors/Kconfig"
|
||||
|
|
|
@ -8,4 +8,5 @@
|
|||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-y += hid-sensors/
|
||||
obj-y += ssp_sensors/
|
||||
obj-y += st_sensors/
|
||||
|
|
|
@ -22,16 +22,18 @@
|
|||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hid-sensor-hub.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include "hid-sensor-trigger.h"
|
||||
|
||||
int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
|
||||
static int _hid_sensor_power_state(struct hid_sensor_common *st, bool state)
|
||||
{
|
||||
int state_val;
|
||||
int report_val;
|
||||
s32 poll_value = 0;
|
||||
|
||||
if (state) {
|
||||
if (sensor_hub_device_open(st->hsdev))
|
||||
|
@ -47,6 +49,8 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
|
|||
st->report_state.report_id,
|
||||
st->report_state.index,
|
||||
HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM);
|
||||
|
||||
poll_value = hid_sensor_read_poll_value(st);
|
||||
} else {
|
||||
if (!atomic_dec_and_test(&st->data_ready))
|
||||
return 0;
|
||||
|
@ -78,10 +82,36 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
|
|||
sensor_hub_get_feature(st->hsdev, st->power_state.report_id,
|
||||
st->power_state.index,
|
||||
&state_val);
|
||||
if (state && poll_value)
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(hid_sensor_power_state);
|
||||
|
||||
int hid_sensor_power_state(struct hid_sensor_common *st, bool state)
|
||||
{
|
||||
#ifdef CONFIG_PM
|
||||
int ret;
|
||||
|
||||
if (state)
|
||||
ret = pm_runtime_get_sync(&st->pdev->dev);
|
||||
else {
|
||||
pm_runtime_mark_last_busy(&st->pdev->dev);
|
||||
ret = pm_runtime_put_autosuspend(&st->pdev->dev);
|
||||
}
|
||||
if (ret < 0) {
|
||||
if (state)
|
||||
pm_runtime_put_noidle(&st->pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return _hid_sensor_power_state(st, state);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int hid_sensor_data_rdy_trigger_set_state(struct iio_trigger *trig,
|
||||
bool state)
|
||||
{
|
||||
|
@ -125,8 +155,21 @@ int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
|
|||
attrb->trigger = trig;
|
||||
indio_dev->trig = iio_trigger_get(trig);
|
||||
|
||||
return ret;
|
||||
ret = pm_runtime_set_active(&indio_dev->dev);
|
||||
if (ret)
|
||||
goto error_unreg_trigger;
|
||||
|
||||
iio_device_set_drvdata(indio_dev, attrb);
|
||||
pm_suspend_ignore_children(&attrb->pdev->dev, true);
|
||||
pm_runtime_enable(&attrb->pdev->dev);
|
||||
/* Default to 3 seconds, but can be changed from sysfs */
|
||||
pm_runtime_set_autosuspend_delay(&attrb->pdev->dev,
|
||||
3000);
|
||||
pm_runtime_use_autosuspend(&attrb->pdev->dev);
|
||||
|
||||
return ret;
|
||||
error_unreg_trigger:
|
||||
iio_trigger_unregister(trig);
|
||||
error_free_trig:
|
||||
iio_trigger_free(trig);
|
||||
error_ret:
|
||||
|
@ -134,6 +177,34 @@ int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
|
|||
}
|
||||
EXPORT_SYMBOL(hid_sensor_setup_trigger);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int hid_sensor_suspend(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
return _hid_sensor_power_state(attrb, false);
|
||||
}
|
||||
|
||||
static int hid_sensor_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
struct hid_sensor_common *attrb = iio_device_get_drvdata(indio_dev);
|
||||
|
||||
return _hid_sensor_power_state(attrb, true);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const struct dev_pm_ops hid_sensor_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(hid_sensor_suspend, hid_sensor_resume)
|
||||
SET_RUNTIME_PM_OPS(hid_sensor_suspend,
|
||||
hid_sensor_resume, NULL)
|
||||
};
|
||||
EXPORT_SYMBOL(hid_sensor_pm_ops);
|
||||
|
||||
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
|
||||
MODULE_DESCRIPTION("HID Sensor trigger processing");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -19,6 +19,11 @@
|
|||
#ifndef _HID_SENSOR_TRIGGER_H
|
||||
#define _HID_SENSOR_TRIGGER_H
|
||||
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
extern const struct dev_pm_ops hid_sensor_pm_ops;
|
||||
|
||||
int hid_sensor_setup_trigger(struct iio_dev *indio_dev, const char *name,
|
||||
struct hid_sensor_common *attrb);
|
||||
void hid_sensor_remove_trigger(struct hid_sensor_common *attrb);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# SSP sensor drivers and commons configuration
|
||||
#
|
||||
menu "SSP Sensor Common"
|
||||
|
||||
config IIO_SSP_SENSORS_COMMONS
|
||||
tristate "Commons for all SSP Sensor IIO drivers"
|
||||
depends on IIO_SSP_SENSORHUB
|
||||
select IIO_BUFFER
|
||||
select IIO_KFIFO_BUF
|
||||
help
|
||||
Say yes here to build commons for SSP sensors.
|
||||
To compile this as a module, choose M here: the module
|
||||
will be called ssp_iio.
|
||||
|
||||
config IIO_SSP_SENSORHUB
|
||||
tristate "Samsung Sensorhub driver"
|
||||
depends on SPI
|
||||
select MFD_CORE
|
||||
help
|
||||
SSP driver for sensorhub.
|
||||
If you say yes here you get ssp support for sensorhub.
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called sensorhub.
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,8 @@
|
|||
#
|
||||
# Makefile for SSP sensor drivers and commons.
|
||||
#
|
||||
|
||||
sensorhub-objs := ssp_dev.o ssp_spi.o
|
||||
obj-$(CONFIG_IIO_SSP_SENSORHUB) += sensorhub.o
|
||||
|
||||
obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_iio.o
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SSP_SENSORHUB_H__
|
||||
#define __SSP_SENSORHUB_H__
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/iio/common/ssp_sensors.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define SSP_DEVICE_ID 0x55
|
||||
|
||||
#ifdef SSP_DBG
|
||||
#define ssp_dbg(format, ...) pr_info("[SSP] "format, ##__VA_ARGS__)
|
||||
#else
|
||||
#define ssp_dbg(format, ...)
|
||||
#endif
|
||||
|
||||
#define SSP_SW_RESET_TIME 3000
|
||||
/* Sensor polling in ms */
|
||||
#define SSP_DEFAULT_POLLING_DELAY 200
|
||||
#define SSP_DEFAULT_RETRIES 3
|
||||
#define SSP_DATA_PACKET_SIZE 960
|
||||
#define SSP_HEADER_BUFFER_SIZE 4
|
||||
|
||||
enum {
|
||||
SSP_KERNEL_BINARY = 0,
|
||||
SSP_KERNEL_CRASHED_BINARY,
|
||||
};
|
||||
|
||||
enum {
|
||||
SSP_INITIALIZATION_STATE = 0,
|
||||
SSP_NO_SENSOR_STATE,
|
||||
SSP_ADD_SENSOR_STATE,
|
||||
SSP_RUNNING_SENSOR_STATE,
|
||||
};
|
||||
|
||||
/* Firmware download STATE */
|
||||
enum {
|
||||
SSP_FW_DL_STATE_FAIL = -1,
|
||||
SSP_FW_DL_STATE_NONE = 0,
|
||||
SSP_FW_DL_STATE_NEED_TO_SCHEDULE,
|
||||
SSP_FW_DL_STATE_SCHEDULED,
|
||||
SSP_FW_DL_STATE_DOWNLOADING,
|
||||
SSP_FW_DL_STATE_SYNC,
|
||||
SSP_FW_DL_STATE_DONE,
|
||||
};
|
||||
|
||||
#define SSP_INVALID_REVISION 99999
|
||||
#define SSP_INVALID_REVISION2 0xffffff
|
||||
|
||||
/* AP -> SSP Instruction */
|
||||
#define SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD 0xa1
|
||||
#define SSP_MSG2SSP_INST_BYPASS_SENSOR_RM 0xa2
|
||||
#define SSP_MSG2SSP_INST_REMOVE_ALL 0xa3
|
||||
#define SSP_MSG2SSP_INST_CHANGE_DELAY 0xa4
|
||||
#define SSP_MSG2SSP_INST_LIBRARY_ADD 0xb1
|
||||
#define SSP_MSG2SSP_INST_LIBRARY_REMOVE 0xb2
|
||||
#define SSP_MSG2SSP_INST_LIB_NOTI 0xb4
|
||||
#define SSP_MSG2SSP_INST_LIB_DATA 0xc1
|
||||
|
||||
#define SSP_MSG2SSP_AP_MCU_SET_GYRO_CAL 0xcd
|
||||
#define SSP_MSG2SSP_AP_MCU_SET_ACCEL_CAL 0xce
|
||||
#define SSP_MSG2SSP_AP_STATUS_SHUTDOWN 0xd0
|
||||
#define SSP_MSG2SSP_AP_STATUS_WAKEUP 0xd1
|
||||
#define SSP_MSG2SSP_AP_STATUS_SLEEP 0xd2
|
||||
#define SSP_MSG2SSP_AP_STATUS_RESUME 0xd3
|
||||
#define SSP_MSG2SSP_AP_STATUS_SUSPEND 0xd4
|
||||
#define SSP_MSG2SSP_AP_STATUS_RESET 0xd5
|
||||
#define SSP_MSG2SSP_AP_STATUS_POW_CONNECTED 0xd6
|
||||
#define SSP_MSG2SSP_AP_STATUS_POW_DISCONNECTED 0xd7
|
||||
#define SSP_MSG2SSP_AP_TEMPHUMIDITY_CAL_DONE 0xda
|
||||
#define SSP_MSG2SSP_AP_MCU_SET_DUMPMODE 0xdb
|
||||
#define SSP_MSG2SSP_AP_MCU_DUMP_CHECK 0xdc
|
||||
#define SSP_MSG2SSP_AP_MCU_BATCH_FLUSH 0xdd
|
||||
#define SSP_MSG2SSP_AP_MCU_BATCH_COUNT 0xdf
|
||||
|
||||
#define SSP_MSG2SSP_AP_WHOAMI 0x0f
|
||||
#define SSP_MSG2SSP_AP_FIRMWARE_REV 0xf0
|
||||
#define SSP_MSG2SSP_AP_SENSOR_FORMATION 0xf1
|
||||
#define SSP_MSG2SSP_AP_SENSOR_PROXTHRESHOLD 0xf2
|
||||
#define SSP_MSG2SSP_AP_SENSOR_BARCODE_EMUL 0xf3
|
||||
#define SSP_MSG2SSP_AP_SENSOR_SCANNING 0xf4
|
||||
#define SSP_MSG2SSP_AP_SET_MAGNETIC_HWOFFSET 0xf5
|
||||
#define SSP_MSG2SSP_AP_GET_MAGNETIC_HWOFFSET 0xf6
|
||||
#define SSP_MSG2SSP_AP_SENSOR_GESTURE_CURRENT 0xf7
|
||||
#define SSP_MSG2SSP_AP_GET_THERM 0xf8
|
||||
#define SSP_MSG2SSP_AP_GET_BIG_DATA 0xf9
|
||||
#define SSP_MSG2SSP_AP_SET_BIG_DATA 0xfa
|
||||
#define SSP_MSG2SSP_AP_START_BIG_DATA 0xfb
|
||||
#define SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX 0xfd
|
||||
#define SSP_MSG2SSP_AP_SENSOR_TILT 0xea
|
||||
#define SSP_MSG2SSP_AP_MCU_SET_TIME 0xfe
|
||||
#define SSP_MSG2SSP_AP_MCU_GET_TIME 0xff
|
||||
|
||||
#define SSP_MSG2SSP_AP_FUSEROM 0x01
|
||||
|
||||
/* voice data */
|
||||
#define SSP_TYPE_WAKE_UP_VOICE_SERVICE 0x01
|
||||
#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_AM 0x01
|
||||
#define SSP_TYPE_WAKE_UP_VOICE_SOUND_SOURCE_GRAMMER 0x02
|
||||
|
||||
/* Factory Test */
|
||||
#define SSP_ACCELEROMETER_FACTORY 0x80
|
||||
#define SSP_GYROSCOPE_FACTORY 0x81
|
||||
#define SSP_GEOMAGNETIC_FACTORY 0x82
|
||||
#define SSP_PRESSURE_FACTORY 0x85
|
||||
#define SSP_GESTURE_FACTORY 0x86
|
||||
#define SSP_TEMPHUMIDITY_CRC_FACTORY 0x88
|
||||
#define SSP_GYROSCOPE_TEMP_FACTORY 0x8a
|
||||
#define SSP_GYROSCOPE_DPS_FACTORY 0x8b
|
||||
#define SSP_MCU_FACTORY 0x8c
|
||||
#define SSP_MCU_SLEEP_FACTORY 0x8d
|
||||
|
||||
/* SSP -> AP ACK about write CMD */
|
||||
#define SSP_MSG_ACK 0x80 /* ACK from SSP to AP */
|
||||
#define SSP_MSG_NAK 0x70 /* NAK from SSP to AP */
|
||||
|
||||
struct ssp_sensorhub_info {
|
||||
char *fw_name;
|
||||
char *fw_crashed_name;
|
||||
unsigned int fw_rev;
|
||||
const u8 * const mag_table;
|
||||
const unsigned int mag_length;
|
||||
};
|
||||
|
||||
/* ssp_msg options bit */
|
||||
#define SSP_RW 0
|
||||
#define SSP_INDEX 3
|
||||
|
||||
#define SSP_AP2HUB_READ 0
|
||||
#define SSP_AP2HUB_WRITE 1
|
||||
#define SSP_HUB2AP_WRITE 2
|
||||
#define SSP_AP2HUB_READY 3
|
||||
#define SSP_AP2HUB_RETURN 4
|
||||
|
||||
/**
|
||||
* struct ssp_data - ssp platformdata structure
|
||||
* @spi: spi device
|
||||
* @sensorhub_info: info about sensorhub board specific features
|
||||
* @wdt_timer: watchdog timer
|
||||
* @work_wdt: watchdog work
|
||||
* @work_firmware: firmware upgrade work queue
|
||||
* @work_refresh: refresh work queue for reset request from MCU
|
||||
* @shut_down: shut down flag
|
||||
* @mcu_dump_mode: mcu dump mode for debug
|
||||
* @time_syncing: time syncing indication flag
|
||||
* @timestamp: previous time in ns calculated for time syncing
|
||||
* @check_status: status table for each sensor
|
||||
* @com_fail_cnt: communication fail count
|
||||
* @reset_cnt: reset count
|
||||
* @timeout_cnt: timeout count
|
||||
* @available_sensors: available sensors seen by sensorhub (bit array)
|
||||
* @cur_firm_rev: cached current firmware revision
|
||||
* @last_resume_state: last AP resume/suspend state used to handle the PM
|
||||
* state of ssp
|
||||
* @last_ap_state: (obsolete) sleep notification for MCU
|
||||
* @sensor_enable: sensor enable mask
|
||||
* @delay_buf: data acquisition intervals table
|
||||
* @batch_latency_buf: yet unknown but existing in communication protocol
|
||||
* @batch_opt_buf: yet unknown but existing in communication protocol
|
||||
* @accel_position: yet unknown but existing in communication protocol
|
||||
* @mag_position: yet unknown but existing in communication protocol
|
||||
* @fw_dl_state: firmware download state
|
||||
* @comm_lock: lock protecting the handshake
|
||||
* @pending_lock: lock protecting pending list and completion
|
||||
* @mcu_reset_gpio: mcu reset line
|
||||
* @ap_mcu_gpio: ap to mcu gpio line
|
||||
* @mcu_ap_gpio: mcu to ap gpio line
|
||||
* @pending_list: pending list for messages queued to be sent/read
|
||||
* @sensor_devs: registered IIO devices table
|
||||
* @enable_refcount: enable reference count for wdt (watchdog timer)
|
||||
* @header_buffer: cache aligned buffer for packet header
|
||||
*/
|
||||
struct ssp_data {
|
||||
struct spi_device *spi;
|
||||
struct ssp_sensorhub_info *sensorhub_info;
|
||||
struct timer_list wdt_timer;
|
||||
struct work_struct work_wdt;
|
||||
struct delayed_work work_refresh;
|
||||
|
||||
bool shut_down;
|
||||
bool mcu_dump_mode;
|
||||
bool time_syncing;
|
||||
int64_t timestamp;
|
||||
|
||||
int check_status[SSP_SENSOR_MAX];
|
||||
|
||||
unsigned int com_fail_cnt;
|
||||
unsigned int reset_cnt;
|
||||
unsigned int timeout_cnt;
|
||||
|
||||
unsigned int available_sensors;
|
||||
unsigned int cur_firm_rev;
|
||||
|
||||
char last_resume_state;
|
||||
char last_ap_state;
|
||||
|
||||
unsigned int sensor_enable;
|
||||
u32 delay_buf[SSP_SENSOR_MAX];
|
||||
s32 batch_latency_buf[SSP_SENSOR_MAX];
|
||||
s8 batch_opt_buf[SSP_SENSOR_MAX];
|
||||
|
||||
int accel_position;
|
||||
int mag_position;
|
||||
int fw_dl_state;
|
||||
|
||||
struct mutex comm_lock;
|
||||
struct mutex pending_lock;
|
||||
|
||||
int mcu_reset_gpio;
|
||||
int ap_mcu_gpio;
|
||||
int mcu_ap_gpio;
|
||||
|
||||
struct list_head pending_list;
|
||||
|
||||
struct iio_dev *sensor_devs[SSP_SENSOR_MAX];
|
||||
atomic_t enable_refcount;
|
||||
|
||||
__le16 header_buffer[SSP_HEADER_BUFFER_SIZE / sizeof(__le16)]
|
||||
____cacheline_aligned;
|
||||
};
|
||||
|
||||
void ssp_clean_pending_list(struct ssp_data *data);
|
||||
|
||||
int ssp_command(struct ssp_data *data, char command, int arg);
|
||||
|
||||
int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type,
|
||||
u8 *send_buf, u8 length);
|
||||
|
||||
int ssp_irq_msg(struct ssp_data *data);
|
||||
|
||||
int ssp_get_chipid(struct ssp_data *data);
|
||||
|
||||
int ssp_set_magnetic_matrix(struct ssp_data *data);
|
||||
|
||||
unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data);
|
||||
|
||||
unsigned int ssp_get_firmware_rev(struct ssp_data *data);
|
||||
|
||||
int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay);
|
||||
|
||||
#endif /* __SSP_SENSORHUB_H__ */
|
|
@ -0,0 +1,712 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include "ssp.h"
|
||||
|
||||
#define SSP_WDT_TIME 10000
|
||||
#define SSP_LIMIT_RESET_CNT 20
|
||||
#define SSP_LIMIT_TIMEOUT_CNT 3
|
||||
|
||||
/* It is possible that it is max clk rate for version 1.0 of bootcode */
|
||||
#define SSP_BOOT_SPI_HZ 400000
|
||||
|
||||
/*
|
||||
* These fields can look enigmatic but this structure is used mainly to flat
|
||||
* some values and depends on command type.
|
||||
*/
|
||||
struct ssp_instruction {
|
||||
__le32 a;
|
||||
__le32 b;
|
||||
u8 c;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67,
|
||||
208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171,
|
||||
243, 13, 45, 250};
|
||||
|
||||
static const struct ssp_sensorhub_info ssp_rinato_info = {
|
||||
.fw_name = "ssp_B2.fw",
|
||||
.fw_crashed_name = "ssp_crashed.fw",
|
||||
.fw_rev = 14052300,
|
||||
.mag_table = ssp_magnitude_table,
|
||||
.mag_length = ARRAY_SIZE(ssp_magnitude_table),
|
||||
};
|
||||
|
||||
static const struct ssp_sensorhub_info ssp_thermostat_info = {
|
||||
.fw_name = "thermostat_B2.fw",
|
||||
.fw_crashed_name = "ssp_crashed.fw",
|
||||
.fw_rev = 14080600,
|
||||
.mag_table = ssp_magnitude_table,
|
||||
.mag_length = ARRAY_SIZE(ssp_magnitude_table),
|
||||
};
|
||||
|
||||
static const struct mfd_cell sensorhub_sensor_devs[] = {
|
||||
{
|
||||
.name = "ssp-accelerometer",
|
||||
},
|
||||
{
|
||||
.name = "ssp-gyroscope",
|
||||
},
|
||||
};
|
||||
|
||||
static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data)
|
||||
{
|
||||
gpio_set_value(data->mcu_reset_gpio, 0);
|
||||
usleep_range(1000, 1200);
|
||||
gpio_set_value(data->mcu_reset_gpio, 1);
|
||||
msleep(50);
|
||||
}
|
||||
|
||||
static void ssp_sync_available_sensors(struct ssp_data *data)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < SSP_SENSOR_MAX; ++i) {
|
||||
if (data->available_sensors & BIT(i)) {
|
||||
ret = ssp_enable_sensor(data, i, data->delay_buf[i]);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"Sync sensor nr: %d fail\n", i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE,
|
||||
data->mcu_dump_mode);
|
||||
if (ret < 0)
|
||||
dev_err(&data->spi->dev,
|
||||
"SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n");
|
||||
}
|
||||
|
||||
static void ssp_enable_mcu(struct ssp_data *data, bool enable)
|
||||
{
|
||||
dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n", enable,
|
||||
data->shut_down);
|
||||
|
||||
if (enable && data->shut_down) {
|
||||
data->shut_down = false;
|
||||
enable_irq(data->spi->irq);
|
||||
enable_irq_wake(data->spi->irq);
|
||||
} else if (!enable && !data->shut_down) {
|
||||
data->shut_down = true;
|
||||
disable_irq(data->spi->irq);
|
||||
disable_irq_wake(data->spi->irq);
|
||||
} else {
|
||||
dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n",
|
||||
enable, data->shut_down);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is the first one which communicates with the mcu so it is
|
||||
* possible that the first attempt will fail
|
||||
*/
|
||||
static int ssp_check_fwbl(struct ssp_data *data)
|
||||
{
|
||||
int retries = 0;
|
||||
|
||||
while (retries++ < 5) {
|
||||
data->cur_firm_rev = ssp_get_firmware_rev(data);
|
||||
if (data->cur_firm_rev == SSP_INVALID_REVISION ||
|
||||
data->cur_firm_rev == SSP_INVALID_REVISION2) {
|
||||
dev_warn(&data->spi->dev,
|
||||
"Invalid revision, trying %d time\n", retries);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->cur_firm_rev == SSP_INVALID_REVISION ||
|
||||
data->cur_firm_rev == SSP_INVALID_REVISION2) {
|
||||
dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n");
|
||||
return SSP_FW_DL_STATE_NEED_TO_SCHEDULE;
|
||||
}
|
||||
|
||||
dev_info(&data->spi->dev,
|
||||
"MCU Firm Rev : Old = %8u, New = %8u\n",
|
||||
data->cur_firm_rev,
|
||||
data->sensorhub_info->fw_rev);
|
||||
|
||||
if (data->cur_firm_rev != data->sensorhub_info->fw_rev)
|
||||
return SSP_FW_DL_STATE_NEED_TO_SCHEDULE;
|
||||
|
||||
return SSP_FW_DL_STATE_NONE;
|
||||
}
|
||||
|
||||
static void ssp_reset_mcu(struct ssp_data *data)
|
||||
{
|
||||
ssp_enable_mcu(data, false);
|
||||
ssp_clean_pending_list(data);
|
||||
ssp_toggle_mcu_reset_gpio(data);
|
||||
ssp_enable_mcu(data, true);
|
||||
}
|
||||
|
||||
static void ssp_wdt_work_func(struct work_struct *work)
|
||||
{
|
||||
struct ssp_data *data = container_of(work, struct ssp_data, work_wdt);
|
||||
|
||||
dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n",
|
||||
__func__, data->available_sensors, data->reset_cnt,
|
||||
data->com_fail_cnt);
|
||||
|
||||
ssp_reset_mcu(data);
|
||||
data->com_fail_cnt = 0;
|
||||
data->timeout_cnt = 0;
|
||||
}
|
||||
|
||||
static void ssp_wdt_timer_func(unsigned long ptr)
|
||||
{
|
||||
struct ssp_data *data = (struct ssp_data *)ptr;
|
||||
|
||||
switch (data->fw_dl_state) {
|
||||
case SSP_FW_DL_STATE_FAIL:
|
||||
case SSP_FW_DL_STATE_DOWNLOADING:
|
||||
case SSP_FW_DL_STATE_SYNC:
|
||||
goto _mod;
|
||||
}
|
||||
|
||||
if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT ||
|
||||
data->com_fail_cnt > SSP_LIMIT_RESET_CNT)
|
||||
queue_work(system_power_efficient_wq, &data->work_wdt);
|
||||
_mod:
|
||||
mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME));
|
||||
}
|
||||
|
||||
static void ssp_enable_wdt_timer(struct ssp_data *data)
|
||||
{
|
||||
mod_timer(&data->wdt_timer, jiffies + msecs_to_jiffies(SSP_WDT_TIME));
|
||||
}
|
||||
|
||||
static void ssp_disable_wdt_timer(struct ssp_data *data)
|
||||
{
|
||||
del_timer_sync(&data->wdt_timer);
|
||||
cancel_work_sync(&data->work_wdt);
|
||||
}
|
||||
|
||||
/**
|
||||
* ssp_get_sensor_delay() - gets sensor data acquisition period
|
||||
* @data: sensorhub structure
|
||||
* @type: SSP sensor type
|
||||
*
|
||||
* Returns acquisition period in ms
|
||||
*/
|
||||
u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type)
|
||||
{
|
||||
return data->delay_buf[type];
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_get_sensor_delay);
|
||||
|
||||
/**
|
||||
* ssp_enable_sensor() - enables data acquisition for sensor
|
||||
* @data: sensorhub structure
|
||||
* @type: SSP sensor type
|
||||
* @delay: delay in ms
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type,
|
||||
u32 delay)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_instruction to_send;
|
||||
|
||||
to_send.a = cpu_to_le32(delay);
|
||||
to_send.b = cpu_to_le32(data->batch_latency_buf[type]);
|
||||
to_send.c = data->batch_opt_buf[type];
|
||||
|
||||
switch (data->check_status[type]) {
|
||||
case SSP_INITIALIZATION_STATE:
|
||||
/* do calibration step, now just enable */
|
||||
case SSP_ADD_SENSOR_STATE:
|
||||
ret = ssp_send_instruction(data,
|
||||
SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD,
|
||||
type,
|
||||
(u8 *)&to_send, sizeof(to_send));
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev, "Enabling sensor failed\n");
|
||||
data->check_status[type] = SSP_NO_SENSOR_STATE;
|
||||
goto derror;
|
||||
}
|
||||
|
||||
data->sensor_enable |= BIT(type);
|
||||
data->check_status[type] = SSP_RUNNING_SENSOR_STATE;
|
||||
break;
|
||||
case SSP_RUNNING_SENSOR_STATE:
|
||||
ret = ssp_send_instruction(data,
|
||||
SSP_MSG2SSP_INST_CHANGE_DELAY, type,
|
||||
(u8 *)&to_send, sizeof(to_send));
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"Changing sensor delay failed\n");
|
||||
goto derror;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
data->check_status[type] = SSP_ADD_SENSOR_STATE;
|
||||
break;
|
||||
}
|
||||
|
||||
data->delay_buf[type] = delay;
|
||||
|
||||
if (atomic_inc_return(&data->enable_refcount) == 1)
|
||||
ssp_enable_wdt_timer(data);
|
||||
|
||||
return 0;
|
||||
|
||||
derror:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_enable_sensor);
|
||||
|
||||
/**
|
||||
* ssp_change_delay() - changes data acquisition for sensor
|
||||
* @data: sensorhub structure
|
||||
* @type: SSP sensor type
|
||||
* @delay: delay in ms
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type,
|
||||
u32 delay)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_instruction to_send;
|
||||
|
||||
to_send.a = cpu_to_le32(delay);
|
||||
to_send.b = cpu_to_le32(data->batch_latency_buf[type]);
|
||||
to_send.c = data->batch_opt_buf[type];
|
||||
|
||||
ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, type,
|
||||
(u8 *)&to_send, sizeof(to_send));
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev, "Changing sensor delay failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->delay_buf[type] = delay;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_change_delay);
|
||||
|
||||
/**
|
||||
* ssp_disable_sensor() - disables sensor
|
||||
*
|
||||
* @data: sensorhub structure
|
||||
* @type: SSP sensor type
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type)
|
||||
{
|
||||
int ret;
|
||||
__le32 command;
|
||||
|
||||
if (data->sensor_enable & BIT(type)) {
|
||||
command = cpu_to_le32(data->delay_buf[type]);
|
||||
|
||||
ret = ssp_send_instruction(data,
|
||||
SSP_MSG2SSP_INST_BYPASS_SENSOR_RM,
|
||||
type, (u8 *)&command,
|
||||
sizeof(command));
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev, "Remove sensor fail\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->sensor_enable &= ~BIT(type);
|
||||
}
|
||||
|
||||
data->check_status[type] = SSP_ADD_SENSOR_STATE;
|
||||
|
||||
if (atomic_dec_and_test(&data->enable_refcount))
|
||||
ssp_disable_wdt_timer(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_disable_sensor);
|
||||
|
||||
static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id)
|
||||
{
|
||||
struct ssp_data *data = dev_id;
|
||||
|
||||
/*
|
||||
* This wrapper is done to preserve error path for ssp_irq_msg, also
|
||||
* it is defined in different file.
|
||||
*/
|
||||
ssp_irq_msg(data);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ssp_initialize_mcu(struct ssp_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ssp_clean_pending_list(data);
|
||||
|
||||
ret = ssp_get_chipid(data);
|
||||
if (ret != SSP_DEVICE_ID) {
|
||||
dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n", __func__,
|
||||
ret < 0 ? "is not working" : "identification failed",
|
||||
ret);
|
||||
return ret < 0 ? ret : -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(&data->spi->dev, "MCU device ID = %d\n", ret);
|
||||
|
||||
/*
|
||||
* needs clarification, for now do not want to export all transfer
|
||||
* methods to sensors' drivers
|
||||
*/
|
||||
ret = ssp_set_magnetic_matrix(data);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"%s - ssp_set_magnetic_matrix failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->available_sensors = ssp_get_sensor_scanning_info(data);
|
||||
if (data->available_sensors == 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"%s - ssp_get_sensor_scanning_info failed\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
data->cur_firm_rev = ssp_get_firmware_rev(data);
|
||||
dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n",
|
||||
data->cur_firm_rev);
|
||||
|
||||
return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* sensorhub can request its reinitialization as some brutal and rare error
|
||||
* handling. It can be requested from the MCU.
|
||||
*/
|
||||
static void ssp_refresh_task(struct work_struct *work)
|
||||
{
|
||||
struct ssp_data *data = container_of((struct delayed_work *)work,
|
||||
struct ssp_data, work_refresh);
|
||||
|
||||
dev_info(&data->spi->dev, "refreshing\n");
|
||||
|
||||
data->reset_cnt++;
|
||||
|
||||
if (ssp_initialize_mcu(data) >= 0) {
|
||||
ssp_sync_available_sensors(data);
|
||||
if (data->last_ap_state != 0)
|
||||
ssp_command(data, data->last_ap_state, 0);
|
||||
|
||||
if (data->last_resume_state != 0)
|
||||
ssp_command(data, data->last_resume_state, 0);
|
||||
|
||||
data->timeout_cnt = 0;
|
||||
data->com_fail_cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay)
|
||||
{
|
||||
cancel_delayed_work_sync(&data->work_refresh);
|
||||
|
||||
return queue_delayed_work(system_power_efficient_wq,
|
||||
&data->work_refresh,
|
||||
msecs_to_jiffies(delay));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct of_device_id ssp_of_match[] = {
|
||||
{
|
||||
.compatible = "samsung,sensorhub-rinato",
|
||||
.data = &ssp_rinato_info,
|
||||
}, {
|
||||
.compatible = "samsung,sensorhub-thermostat",
|
||||
.data = &ssp_thermostat_info,
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ssp_of_match);
|
||||
|
||||
static struct ssp_data *ssp_parse_dt(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_data *data;
|
||||
struct device_node *node = dev->of_node;
|
||||
const struct of_device_id *match;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return NULL;
|
||||
|
||||
data->mcu_ap_gpio = of_get_named_gpio(node, "mcu-ap-gpios", 0);
|
||||
if (data->mcu_ap_gpio < 0)
|
||||
goto err_free_pd;
|
||||
|
||||
data->ap_mcu_gpio = of_get_named_gpio(node, "ap-mcu-gpios", 0);
|
||||
if (data->ap_mcu_gpio < 0)
|
||||
goto err_free_pd;
|
||||
|
||||
data->mcu_reset_gpio = of_get_named_gpio(node, "mcu-reset-gpios", 0);
|
||||
if (data->mcu_reset_gpio < 0)
|
||||
goto err_free_pd;
|
||||
|
||||
ret = devm_gpio_request_one(dev, data->ap_mcu_gpio, GPIOF_OUT_INIT_HIGH,
|
||||
"ap-mcu-gpios");
|
||||
if (ret)
|
||||
goto err_free_pd;
|
||||
|
||||
ret = devm_gpio_request_one(dev, data->mcu_reset_gpio,
|
||||
GPIOF_OUT_INIT_HIGH, "mcu-reset-gpios");
|
||||
if (ret)
|
||||
goto err_ap_mcu;
|
||||
|
||||
match = of_match_node(ssp_of_match, node);
|
||||
if (!match)
|
||||
goto err_mcu_reset_gpio;
|
||||
|
||||
data->sensorhub_info = (struct ssp_sensorhub_info *)match->data;
|
||||
|
||||
dev_set_drvdata(dev, data);
|
||||
|
||||
return data;
|
||||
|
||||
err_mcu_reset_gpio:
|
||||
devm_gpio_free(dev, data->mcu_reset_gpio);
|
||||
err_ap_mcu:
|
||||
devm_gpio_free(dev, data->ap_mcu_gpio);
|
||||
err_free_pd:
|
||||
devm_kfree(dev, data);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
static struct ssp_data *ssp_parse_dt(struct device *pdev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* ssp_register_consumer() - registers iio consumer in ssp framework
|
||||
*
|
||||
* @indio_dev: consumer iio device
|
||||
* @type: ssp sensor type
|
||||
*/
|
||||
void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type)
|
||||
{
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
data->sensor_devs[type] = indio_dev;
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_register_consumer);
|
||||
|
||||
static int ssp_probe(struct spi_device *spi)
|
||||
{
|
||||
int ret, i;
|
||||
struct ssp_data *data;
|
||||
|
||||
data = ssp_parse_dt(&spi->dev);
|
||||
if (!data) {
|
||||
dev_err(&spi->dev, "Failed to find platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(&spi->dev, -1, sensorhub_sensor_devs,
|
||||
ARRAY_SIZE(sensorhub_sensor_devs), NULL, 0, NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "mfd add devices fail\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi->mode = SPI_MODE_1;
|
||||
ret = spi_setup(spi);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "Failed to setup spi\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->fw_dl_state = SSP_FW_DL_STATE_NONE;
|
||||
data->spi = spi;
|
||||
spi_set_drvdata(spi, data);
|
||||
|
||||
mutex_init(&data->comm_lock);
|
||||
|
||||
for (i = 0; i < SSP_SENSOR_MAX; ++i) {
|
||||
data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY;
|
||||
data->batch_latency_buf[i] = 0;
|
||||
data->batch_opt_buf[i] = 0;
|
||||
data->check_status[i] = SSP_INITIALIZATION_STATE;
|
||||
}
|
||||
|
||||
data->delay_buf[SSP_BIO_HRM_LIB] = 100;
|
||||
|
||||
data->time_syncing = true;
|
||||
|
||||
mutex_init(&data->pending_lock);
|
||||
INIT_LIST_HEAD(&data->pending_list);
|
||||
|
||||
atomic_set(&data->enable_refcount, 0);
|
||||
|
||||
INIT_WORK(&data->work_wdt, ssp_wdt_work_func);
|
||||
INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task);
|
||||
|
||||
setup_timer(&data->wdt_timer, ssp_wdt_timer_func, (unsigned long)data);
|
||||
|
||||
ret = request_threaded_irq(data->spi->irq, NULL,
|
||||
ssp_irq_thread_fn,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"SSP_Int", data);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "Irq request fail\n");
|
||||
goto err_setup_irq;
|
||||
}
|
||||
|
||||
/* Let's start with enabled one so irq balance could be ok */
|
||||
data->shut_down = false;
|
||||
|
||||
/* just to avoid unbalanced irq set wake up */
|
||||
enable_irq_wake(data->spi->irq);
|
||||
|
||||
data->fw_dl_state = ssp_check_fwbl(data);
|
||||
if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) {
|
||||
ret = ssp_initialize_mcu(data);
|
||||
if (ret < 0) {
|
||||
dev_err(&spi->dev, "Initialize_mcu failed\n");
|
||||
goto err_read_reg;
|
||||
}
|
||||
} else {
|
||||
dev_err(&spi->dev, "Firmware version not supported\n");
|
||||
ret = -EPERM;
|
||||
goto err_read_reg;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_read_reg:
|
||||
free_irq(data->spi->irq, data);
|
||||
err_setup_irq:
|
||||
mutex_destroy(&data->pending_lock);
|
||||
mutex_destroy(&data->comm_lock);
|
||||
|
||||
dev_err(&spi->dev, "Probe failed!\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ssp_remove(struct spi_device *spi)
|
||||
{
|
||||
struct ssp_data *data = spi_get_drvdata(spi);
|
||||
|
||||
if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, 0) < 0)
|
||||
dev_err(&data->spi->dev,
|
||||
"SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n");
|
||||
|
||||
ssp_enable_mcu(data, false);
|
||||
ssp_disable_wdt_timer(data);
|
||||
|
||||
ssp_clean_pending_list(data);
|
||||
|
||||
free_irq(data->spi->irq, data);
|
||||
|
||||
del_timer_sync(&data->wdt_timer);
|
||||
cancel_work_sync(&data->work_wdt);
|
||||
|
||||
mutex_destroy(&data->comm_lock);
|
||||
mutex_destroy(&data->pending_lock);
|
||||
|
||||
mfd_remove_devices(&spi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_suspend(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
|
||||
|
||||
data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND;
|
||||
|
||||
if (atomic_read(&data->enable_refcount) > 0)
|
||||
ssp_disable_wdt_timer(data);
|
||||
|
||||
ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n", __func__);
|
||||
|
||||
ssp_enable_wdt_timer(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->time_syncing = false;
|
||||
disable_irq(data->spi->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_resume(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_data *data = spi_get_drvdata(to_spi_device(dev));
|
||||
|
||||
enable_irq(data->spi->irq);
|
||||
|
||||
if (atomic_read(&data->enable_refcount) > 0)
|
||||
ssp_enable_wdt_timer(data);
|
||||
|
||||
ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->spi->dev,
|
||||
"%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n", __func__);
|
||||
ssp_disable_wdt_timer(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* timesyncing is set by MCU */
|
||||
data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ssp_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(ssp_suspend, ssp_resume)
|
||||
};
|
||||
|
||||
static struct spi_driver ssp_driver = {
|
||||
.probe = ssp_probe,
|
||||
.remove = ssp_remove,
|
||||
.driver = {
|
||||
.pm = &ssp_pm_ops,
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(ssp_of_match),
|
||||
.name = "sensorhub"
|
||||
},
|
||||
};
|
||||
|
||||
module_spi_driver(ssp_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ssp sensorhub driver");
|
||||
MODULE_AUTHOR("Samsung Electronics");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/iio/common/ssp_sensors.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include "ssp_iio_sensor.h"
|
||||
|
||||
/**
|
||||
* ssp_common_buffer_postenable() - generic postenable callback for ssp buffer
|
||||
*
|
||||
* @indio_dev: iio device
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_common_buffer_postenable(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct ssp_sensor_data *spd = iio_priv(indio_dev);
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
/* the allocation is made in post because scan size is known in this
|
||||
* moment
|
||||
* */
|
||||
spd->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL | GFP_DMA);
|
||||
if (!spd->buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
return ssp_enable_sensor(data, spd->type,
|
||||
ssp_get_sensor_delay(data, spd->type));
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_common_buffer_postenable);
|
||||
|
||||
/**
|
||||
* ssp_common_buffer_postdisable() - generic postdisable callback for ssp buffer
|
||||
*
|
||||
* @indio_dev: iio device
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_common_buffer_postdisable(struct iio_dev *indio_dev)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_sensor_data *spd = iio_priv(indio_dev);
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
ret = ssp_disable_sensor(data, spd->type);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
kfree(spd->buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_common_buffer_postdisable);
|
||||
|
||||
/**
|
||||
* ssp_common_process_data() - Common process data callback for ssp sensors
|
||||
*
|
||||
* @indio_dev: iio device
|
||||
* @buf: source buffer
|
||||
* @len: sensor data length
|
||||
* @timestamp: system timestamp
|
||||
*
|
||||
* Returns 0 or negative value in case of error
|
||||
*/
|
||||
int ssp_common_process_data(struct iio_dev *indio_dev, void *buf,
|
||||
unsigned int len, int64_t timestamp)
|
||||
{
|
||||
__le32 time;
|
||||
int64_t calculated_time;
|
||||
struct ssp_sensor_data *spd = iio_priv(indio_dev);
|
||||
|
||||
if (indio_dev->scan_bytes == 0)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* it always sends full set of samples, remember about available masks
|
||||
*/
|
||||
memcpy(spd->buffer, buf, len);
|
||||
|
||||
if (indio_dev->scan_timestamp) {
|
||||
memcpy(&time, &((char *)buf)[len], SSP_TIME_SIZE);
|
||||
calculated_time =
|
||||
timestamp + (int64_t)le32_to_cpu(time) * 1000000;
|
||||
}
|
||||
|
||||
return iio_push_to_buffers_with_timestamp(indio_dev, spd->buffer,
|
||||
calculated_time);
|
||||
}
|
||||
EXPORT_SYMBOL(ssp_common_process_data);
|
||||
|
||||
MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>");
|
||||
MODULE_DESCRIPTION("Samsung sensorhub commons");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef __SSP_IIO_SENSOR_H__
|
||||
#define __SSP_IIO_SENSOR_H__
|
||||
|
||||
#define SSP_CHANNEL_AG(_type, _mod, _index) \
|
||||
{ \
|
||||
.type = _type,\
|
||||
.modified = 1,\
|
||||
.channel2 = _mod,\
|
||||
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
|
||||
.scan_index = _index,\
|
||||
.scan_type = {\
|
||||
.sign = 's',\
|
||||
.realbits = 16,\
|
||||
.storagebits = 16,\
|
||||
.shift = 0,\
|
||||
.endianness = IIO_LE,\
|
||||
},\
|
||||
}
|
||||
|
||||
/* It is defined here as it is a mixed timestamp */
|
||||
#define SSP_CHAN_TIMESTAMP(_si) { \
|
||||
.type = IIO_TIMESTAMP, \
|
||||
.channel = -1, \
|
||||
.scan_index = _si, \
|
||||
.scan_type = { \
|
||||
.sign = 's', \
|
||||
.realbits = 64, \
|
||||
.storagebits = 64, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define SSP_MS_PER_S 1000
|
||||
#define SSP_INVERTED_SCALING_FACTOR 1000000U
|
||||
|
||||
#define SSP_FACTOR_WITH_MS \
|
||||
(SSP_INVERTED_SCALING_FACTOR * SSP_MS_PER_S)
|
||||
|
||||
int ssp_common_buffer_postenable(struct iio_dev *indio_dev);
|
||||
|
||||
int ssp_common_buffer_postdisable(struct iio_dev *indio_dev);
|
||||
|
||||
int ssp_common_process_data(struct iio_dev *indio_dev, void *buf,
|
||||
unsigned int len, int64_t timestamp);
|
||||
|
||||
/* Converts time in ms to frequency */
|
||||
static inline void ssp_convert_to_freq(u32 time, int *integer_part,
|
||||
int *fractional)
|
||||
{
|
||||
if (time == 0) {
|
||||
*fractional = 0;
|
||||
*integer_part = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
*integer_part = SSP_FACTOR_WITH_MS / time;
|
||||
*fractional = *integer_part % SSP_INVERTED_SCALING_FACTOR;
|
||||
*integer_part = *integer_part / SSP_INVERTED_SCALING_FACTOR;
|
||||
}
|
||||
|
||||
/* Converts frequency to time in ms */
|
||||
static inline int ssp_convert_to_time(int integer_part, int fractional)
|
||||
{
|
||||
u64 value;
|
||||
|
||||
value = (u64)integer_part * SSP_INVERTED_SCALING_FACTOR + fractional;
|
||||
if (value == 0)
|
||||
return 0;
|
||||
|
||||
return div64_u64((u64)SSP_FACTOR_WITH_MS, value);
|
||||
}
|
||||
#endif /* __SSP_IIO_SENSOR_H__ */
|
|
@ -0,0 +1,608 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ssp.h"
|
||||
|
||||
#define SSP_DEV (&data->spi->dev)
|
||||
#define SSP_GET_MESSAGE_TYPE(data) (data & (3 << SSP_RW))
|
||||
|
||||
/*
|
||||
* SSP -> AP Instruction
|
||||
* They tell what packet type can be expected. In the future there will
|
||||
* be less of them. BYPASS means common sensor packets with accel, gyro,
|
||||
* hrm etc. data. LIBRARY and META are mock-up's for now.
|
||||
*/
|
||||
#define SSP_MSG2AP_INST_BYPASS_DATA 0x37
|
||||
#define SSP_MSG2AP_INST_LIBRARY_DATA 0x01
|
||||
#define SSP_MSG2AP_INST_DEBUG_DATA 0x03
|
||||
#define SSP_MSG2AP_INST_BIG_DATA 0x04
|
||||
#define SSP_MSG2AP_INST_META_DATA 0x05
|
||||
#define SSP_MSG2AP_INST_TIME_SYNC 0x06
|
||||
#define SSP_MSG2AP_INST_RESET 0x07
|
||||
|
||||
#define SSP_UNIMPLEMENTED -1
|
||||
|
||||
struct ssp_msg_header {
|
||||
u8 cmd;
|
||||
__le16 length;
|
||||
__le16 options;
|
||||
__le32 data;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct ssp_msg {
|
||||
u16 length;
|
||||
u16 options;
|
||||
struct list_head list;
|
||||
struct completion *done;
|
||||
struct ssp_msg_header *h;
|
||||
char *buffer;
|
||||
};
|
||||
|
||||
static const int ssp_offset_map[SSP_SENSOR_MAX] = {
|
||||
[SSP_ACCELEROMETER_SENSOR] = SSP_ACCELEROMETER_SIZE +
|
||||
SSP_TIME_SIZE,
|
||||
[SSP_GYROSCOPE_SENSOR] = SSP_GYROSCOPE_SIZE +
|
||||
SSP_TIME_SIZE,
|
||||
[SSP_GEOMAGNETIC_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_GEOMAGNETIC_RAW] = SSP_UNIMPLEMENTED,
|
||||
[SSP_GEOMAGNETIC_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_PRESSURE_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_GESTURE_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_PROXIMITY_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_TEMPERATURE_HUMIDITY_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_LIGHT_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_PROXIMITY_RAW] = SSP_UNIMPLEMENTED,
|
||||
[SSP_ORIENTATION_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_STEP_DETECTOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_SIG_MOTION_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_GYRO_UNCALIB_SENSOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_GAME_ROTATION_VECTOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_ROTATION_VECTOR] = SSP_UNIMPLEMENTED,
|
||||
[SSP_STEP_COUNTER] = SSP_UNIMPLEMENTED,
|
||||
[SSP_BIO_HRM_RAW] = SSP_BIO_HRM_RAW_SIZE +
|
||||
SSP_TIME_SIZE,
|
||||
[SSP_BIO_HRM_RAW_FAC] = SSP_BIO_HRM_RAW_FAC_SIZE +
|
||||
SSP_TIME_SIZE,
|
||||
[SSP_BIO_HRM_LIB] = SSP_BIO_HRM_LIB_SIZE +
|
||||
SSP_TIME_SIZE,
|
||||
};
|
||||
|
||||
#define SSP_HEADER_SIZE (sizeof(struct ssp_msg_header))
|
||||
#define SSP_HEADER_SIZE_ALIGNED (ALIGN(SSP_HEADER_SIZE, 4))
|
||||
|
||||
static struct ssp_msg *ssp_create_msg(u8 cmd, u16 len, u16 opt, u32 data)
|
||||
{
|
||||
struct ssp_msg_header h;
|
||||
struct ssp_msg *msg;
|
||||
|
||||
msg = kzalloc(sizeof(*msg), GFP_KERNEL);
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
h.cmd = cmd;
|
||||
h.length = cpu_to_le16(len);
|
||||
h.options = cpu_to_le16(opt);
|
||||
h.data = cpu_to_le32(data);
|
||||
|
||||
msg->buffer = kzalloc(SSP_HEADER_SIZE_ALIGNED + len,
|
||||
GFP_KERNEL | GFP_DMA);
|
||||
if (!msg->buffer) {
|
||||
kfree(msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
msg->length = len;
|
||||
msg->options = opt;
|
||||
|
||||
memcpy(msg->buffer, &h, SSP_HEADER_SIZE);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is a bit heavy to do it this way but often the function is used to compose
|
||||
* the message from smaller chunks which are placed on the stack. Often the
|
||||
* chunks are small so memcpy should be optimalized.
|
||||
*/
|
||||
static inline void ssp_fill_buffer(struct ssp_msg *m, unsigned int offset,
|
||||
const void *src, unsigned int len)
|
||||
{
|
||||
memcpy(&m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], src, len);
|
||||
}
|
||||
|
||||
static inline void ssp_get_buffer(struct ssp_msg *m, unsigned int offset,
|
||||
void *dest, unsigned int len)
|
||||
{
|
||||
memcpy(dest, &m->buffer[SSP_HEADER_SIZE_ALIGNED + offset], len);
|
||||
}
|
||||
|
||||
#define SSP_GET_BUFFER_AT_INDEX(m, index) \
|
||||
(m->buffer[SSP_HEADER_SIZE_ALIGNED + index])
|
||||
#define SSP_SET_BUFFER_AT_INDEX(m, index, val) \
|
||||
(m->buffer[SSP_HEADER_SIZE_ALIGNED + index] = val)
|
||||
|
||||
static void ssp_clean_msg(struct ssp_msg *m)
|
||||
{
|
||||
kfree(m->buffer);
|
||||
kfree(m);
|
||||
}
|
||||
|
||||
static int ssp_print_mcu_debug(char *data_frame, int *data_index,
|
||||
int received_len)
|
||||
{
|
||||
int length = data_frame[(*data_index)++];
|
||||
|
||||
if (length > received_len - *data_index || length <= 0) {
|
||||
ssp_dbg("[SSP]: MSG From MCU-invalid debug length(%d/%d)\n",
|
||||
length, received_len);
|
||||
return length ? length : -EPROTO;
|
||||
}
|
||||
|
||||
ssp_dbg("[SSP]: MSG From MCU - %s\n", &data_frame[*data_index]);
|
||||
|
||||
*data_index += length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* It was designed that way - additional lines to some kind of handshake,
|
||||
* please do not ask why - only the firmware guy can know it.
|
||||
*/
|
||||
static int ssp_check_lines(struct ssp_data *data, bool state)
|
||||
{
|
||||
int delay_cnt = 0;
|
||||
|
||||
gpio_set_value_cansleep(data->ap_mcu_gpio, state);
|
||||
|
||||
while (gpio_get_value_cansleep(data->mcu_ap_gpio) != state) {
|
||||
usleep_range(3000, 3500);
|
||||
|
||||
if (data->shut_down || delay_cnt++ > 500) {
|
||||
dev_err(SSP_DEV, "%s:timeout, hw ack wait fail %d\n",
|
||||
__func__, state);
|
||||
|
||||
if (!state)
|
||||
gpio_set_value_cansleep(data->ap_mcu_gpio, 1);
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_do_transfer(struct ssp_data *data, struct ssp_msg *msg,
|
||||
struct completion *done, int timeout)
|
||||
{
|
||||
int status;
|
||||
/*
|
||||
* check if this is a short one way message or the whole transfer has
|
||||
* second part after an interrupt
|
||||
*/
|
||||
const bool use_no_irq = msg->length == 0;
|
||||
|
||||
if (data->shut_down)
|
||||
return -EPERM;
|
||||
|
||||
msg->done = done;
|
||||
|
||||
mutex_lock(&data->comm_lock);
|
||||
|
||||
status = ssp_check_lines(data, false);
|
||||
if (status < 0)
|
||||
goto _error_locked;
|
||||
|
||||
status = spi_write(data->spi, msg->buffer, SSP_HEADER_SIZE);
|
||||
if (status < 0) {
|
||||
gpio_set_value_cansleep(data->ap_mcu_gpio, 1);
|
||||
dev_err(SSP_DEV, "%s spi_write fail\n", __func__);
|
||||
goto _error_locked;
|
||||
}
|
||||
|
||||
if (!use_no_irq) {
|
||||
mutex_lock(&data->pending_lock);
|
||||
list_add_tail(&msg->list, &data->pending_list);
|
||||
mutex_unlock(&data->pending_lock);
|
||||
}
|
||||
|
||||
status = ssp_check_lines(data, true);
|
||||
if (status < 0) {
|
||||
if (!use_no_irq) {
|
||||
mutex_lock(&data->pending_lock);
|
||||
list_del(&msg->list);
|
||||
mutex_unlock(&data->pending_lock);
|
||||
}
|
||||
goto _error_locked;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->comm_lock);
|
||||
|
||||
if (!use_no_irq && done)
|
||||
if (wait_for_completion_timeout(done,
|
||||
msecs_to_jiffies(timeout)) ==
|
||||
0) {
|
||||
mutex_lock(&data->pending_lock);
|
||||
list_del(&msg->list);
|
||||
mutex_unlock(&data->pending_lock);
|
||||
|
||||
data->timeout_cnt++;
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
_error_locked:
|
||||
mutex_unlock(&data->comm_lock);
|
||||
data->timeout_cnt++;
|
||||
return status;
|
||||
}
|
||||
|
||||
static inline int ssp_spi_sync_command(struct ssp_data *data,
|
||||
struct ssp_msg *msg)
|
||||
{
|
||||
return ssp_do_transfer(data, msg, NULL, 0);
|
||||
}
|
||||
|
||||
static int ssp_spi_sync(struct ssp_data *data, struct ssp_msg *msg,
|
||||
int timeout)
|
||||
{
|
||||
DECLARE_COMPLETION_ONSTACK(done);
|
||||
|
||||
if (WARN_ON(!msg->length))
|
||||
return -EPERM;
|
||||
|
||||
return ssp_do_transfer(data, msg, &done, timeout);
|
||||
}
|
||||
|
||||
static int ssp_handle_big_data(struct ssp_data *data, char *dataframe, int *idx)
|
||||
{
|
||||
/* mock-up, it will be changed with adding another sensor types */
|
||||
*idx += 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_parse_dataframe(struct ssp_data *data, char *dataframe, int len)
|
||||
{
|
||||
int idx, sd;
|
||||
struct timespec ts;
|
||||
struct ssp_sensor_data *spd;
|
||||
struct iio_dev **indio_devs = data->sensor_devs;
|
||||
|
||||
getnstimeofday(&ts);
|
||||
|
||||
for (idx = 0; idx < len;) {
|
||||
switch (dataframe[idx++]) {
|
||||
case SSP_MSG2AP_INST_BYPASS_DATA:
|
||||
sd = dataframe[idx++];
|
||||
if (sd < 0 || sd >= SSP_SENSOR_MAX) {
|
||||
dev_err(SSP_DEV,
|
||||
"Mcu data frame1 error %d\n", sd);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
if (indio_devs[sd]) {
|
||||
spd = iio_priv(indio_devs[sd]);
|
||||
if (spd->process_data)
|
||||
spd->process_data(indio_devs[sd],
|
||||
&dataframe[idx],
|
||||
data->timestamp);
|
||||
} else {
|
||||
dev_err(SSP_DEV, "no client for frame\n");
|
||||
}
|
||||
|
||||
idx += ssp_offset_map[sd];
|
||||
break;
|
||||
case SSP_MSG2AP_INST_DEBUG_DATA:
|
||||
sd = ssp_print_mcu_debug(dataframe, &idx, len);
|
||||
if (sd) {
|
||||
dev_err(SSP_DEV,
|
||||
"Mcu data frame3 error %d\n", sd);
|
||||
return sd;
|
||||
}
|
||||
break;
|
||||
case SSP_MSG2AP_INST_LIBRARY_DATA:
|
||||
idx += len;
|
||||
break;
|
||||
case SSP_MSG2AP_INST_BIG_DATA:
|
||||
ssp_handle_big_data(data, dataframe, &idx);
|
||||
break;
|
||||
case SSP_MSG2AP_INST_TIME_SYNC:
|
||||
data->time_syncing = true;
|
||||
break;
|
||||
case SSP_MSG2AP_INST_RESET:
|
||||
ssp_queue_ssp_refresh_task(data, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->time_syncing)
|
||||
data->timestamp = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* threaded irq */
|
||||
int ssp_irq_msg(struct ssp_data *data)
|
||||
{
|
||||
bool found = false;
|
||||
char *buffer;
|
||||
u8 msg_type;
|
||||
int ret;
|
||||
u16 length, msg_options;
|
||||
struct ssp_msg *msg, *n;
|
||||
|
||||
ret = spi_read(data->spi, data->header_buffer, SSP_HEADER_BUFFER_SIZE);
|
||||
if (ret < 0) {
|
||||
dev_err(SSP_DEV, "header read fail\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
length = le16_to_cpu(data->header_buffer[1]);
|
||||
msg_options = le16_to_cpu(data->header_buffer[0]);
|
||||
|
||||
if (length == 0) {
|
||||
dev_err(SSP_DEV, "length received from mcu is 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg_type = SSP_GET_MESSAGE_TYPE(msg_options);
|
||||
|
||||
switch (msg_type) {
|
||||
case SSP_AP2HUB_READ:
|
||||
case SSP_AP2HUB_WRITE:
|
||||
/*
|
||||
* this is a small list, a few elements - the packets can be
|
||||
* received with no order
|
||||
*/
|
||||
mutex_lock(&data->pending_lock);
|
||||
list_for_each_entry_safe(msg, n, &data->pending_list, list) {
|
||||
if (msg->options == msg_options) {
|
||||
list_del(&msg->list);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
/*
|
||||
* here can be implemented dead messages handling
|
||||
* but the slave should not send such ones - it is to
|
||||
* check but let's handle this
|
||||
*/
|
||||
buffer = kmalloc(length, GFP_KERNEL | GFP_DMA);
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto _unlock;
|
||||
}
|
||||
|
||||
/* got dead packet so it is always an error */
|
||||
ret = spi_read(data->spi, buffer, length);
|
||||
if (ret >= 0)
|
||||
ret = -EPROTO;
|
||||
|
||||
kfree(buffer);
|
||||
|
||||
dev_err(SSP_DEV, "No match error %x\n",
|
||||
msg_options);
|
||||
|
||||
goto _unlock;
|
||||
}
|
||||
|
||||
if (msg_type == SSP_AP2HUB_READ)
|
||||
ret = spi_read(data->spi,
|
||||
&msg->buffer[SSP_HEADER_SIZE_ALIGNED],
|
||||
msg->length);
|
||||
|
||||
if (msg_type == SSP_AP2HUB_WRITE) {
|
||||
ret = spi_write(data->spi,
|
||||
&msg->buffer[SSP_HEADER_SIZE_ALIGNED],
|
||||
msg->length);
|
||||
if (msg_options & SSP_AP2HUB_RETURN) {
|
||||
msg->options =
|
||||
SSP_AP2HUB_READ | SSP_AP2HUB_RETURN;
|
||||
msg->length = 1;
|
||||
|
||||
list_add_tail(&msg->list, &data->pending_list);
|
||||
goto _unlock;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg->done)
|
||||
if (!completion_done(msg->done))
|
||||
complete(msg->done);
|
||||
_unlock:
|
||||
mutex_unlock(&data->pending_lock);
|
||||
break;
|
||||
case SSP_HUB2AP_WRITE:
|
||||
buffer = kzalloc(length, GFP_KERNEL | GFP_DMA);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = spi_read(data->spi, buffer, length);
|
||||
if (ret < 0) {
|
||||
dev_err(SSP_DEV, "spi read fail\n");
|
||||
kfree(buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ssp_parse_dataframe(data, buffer, length);
|
||||
|
||||
kfree(buffer);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(SSP_DEV, "unknown msg type\n");
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ssp_clean_pending_list(struct ssp_data *data)
|
||||
{
|
||||
struct ssp_msg *msg, *n;
|
||||
|
||||
mutex_lock(&data->pending_lock);
|
||||
list_for_each_entry_safe(msg, n, &data->pending_list, list) {
|
||||
list_del(&msg->list);
|
||||
|
||||
if (msg->done)
|
||||
if (!completion_done(msg->done))
|
||||
complete(msg->done);
|
||||
}
|
||||
mutex_unlock(&data->pending_lock);
|
||||
}
|
||||
|
||||
int ssp_command(struct ssp_data *data, char command, int arg)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_msg *msg;
|
||||
|
||||
msg = ssp_create_msg(command, 0, SSP_AP2HUB_WRITE, arg);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
ssp_dbg("%s - command 0x%x %d\n", __func__, command, arg);
|
||||
|
||||
ret = ssp_spi_sync_command(data, msg);
|
||||
ssp_clean_msg(msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ssp_send_instruction(struct ssp_data *data, u8 inst, u8 sensor_type,
|
||||
u8 *send_buf, u8 length)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_msg *msg;
|
||||
|
||||
if (data->fw_dl_state == SSP_FW_DL_STATE_DOWNLOADING) {
|
||||
dev_err(SSP_DEV, "%s - Skip Inst! DL state = %d\n",
|
||||
__func__, data->fw_dl_state);
|
||||
return -EBUSY;
|
||||
} else if (!(data->available_sensors & BIT(sensor_type)) &&
|
||||
(inst <= SSP_MSG2SSP_INST_CHANGE_DELAY)) {
|
||||
dev_err(SSP_DEV, "%s - Bypass Inst Skip! - %u\n",
|
||||
__func__, sensor_type);
|
||||
return -EIO; /* just fail */
|
||||
}
|
||||
|
||||
msg = ssp_create_msg(inst, length + 2, SSP_AP2HUB_WRITE, 0);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
ssp_fill_buffer(msg, 0, &sensor_type, 1);
|
||||
ssp_fill_buffer(msg, 1, send_buf, length);
|
||||
|
||||
ssp_dbg("%s - Inst = 0x%x, Sensor Type = 0x%x, data = %u\n",
|
||||
__func__, inst, sensor_type, send_buf[1]);
|
||||
|
||||
ret = ssp_spi_sync(data, msg, 1000);
|
||||
ssp_clean_msg(msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ssp_get_chipid(struct ssp_data *data)
|
||||
{
|
||||
int ret;
|
||||
char buffer;
|
||||
struct ssp_msg *msg;
|
||||
|
||||
msg = ssp_create_msg(SSP_MSG2SSP_AP_WHOAMI, 1, SSP_AP2HUB_READ, 0);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = ssp_spi_sync(data, msg, 1000);
|
||||
|
||||
buffer = SSP_GET_BUFFER_AT_INDEX(msg, 0);
|
||||
|
||||
ssp_clean_msg(msg);
|
||||
|
||||
return ret < 0 ? ret : buffer;
|
||||
}
|
||||
|
||||
int ssp_set_magnetic_matrix(struct ssp_data *data)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_msg *msg;
|
||||
|
||||
msg = ssp_create_msg(SSP_MSG2SSP_AP_SET_MAGNETIC_STATIC_MATRIX,
|
||||
data->sensorhub_info->mag_length, SSP_AP2HUB_WRITE,
|
||||
0);
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
ssp_fill_buffer(msg, 0, data->sensorhub_info->mag_table,
|
||||
data->sensorhub_info->mag_length);
|
||||
|
||||
ret = ssp_spi_sync(data, msg, 1000);
|
||||
ssp_clean_msg(msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned int ssp_get_sensor_scanning_info(struct ssp_data *data)
|
||||
{
|
||||
int ret;
|
||||
__le32 result;
|
||||
u32 cpu_result = 0;
|
||||
|
||||
struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_SENSOR_SCANNING, 4,
|
||||
SSP_AP2HUB_READ, 0);
|
||||
if (!msg)
|
||||
return 0;
|
||||
|
||||
ret = ssp_spi_sync(data, msg, 1000);
|
||||
if (ret < 0) {
|
||||
dev_err(SSP_DEV, "%s - spi read fail %d\n", __func__, ret);
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
ssp_get_buffer(msg, 0, &result, 4);
|
||||
cpu_result = le32_to_cpu(result);
|
||||
|
||||
dev_info(SSP_DEV, "%s state: 0x%08x\n", __func__, cpu_result);
|
||||
|
||||
_exit:
|
||||
ssp_clean_msg(msg);
|
||||
return cpu_result;
|
||||
}
|
||||
|
||||
unsigned int ssp_get_firmware_rev(struct ssp_data *data)
|
||||
{
|
||||
int ret;
|
||||
__le32 result;
|
||||
|
||||
struct ssp_msg *msg = ssp_create_msg(SSP_MSG2SSP_AP_FIRMWARE_REV, 4,
|
||||
SSP_AP2HUB_READ, 0);
|
||||
if (!msg)
|
||||
return SSP_INVALID_REVISION;
|
||||
|
||||
ret = ssp_spi_sync(data, msg, 1000);
|
||||
if (ret < 0) {
|
||||
dev_err(SSP_DEV, "%s - transfer fail %d\n", __func__, ret);
|
||||
ret = SSP_INVALID_REVISION;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
ssp_get_buffer(msg, 0, &result, 4);
|
||||
ret = le32_to_cpu(result);
|
||||
|
||||
_exit:
|
||||
ssp_clean_msg(msg);
|
||||
return ret;
|
||||
}
|
|
@ -54,7 +54,7 @@ static int st_sensors_spi_read(struct st_sensor_transfer_buffer *tb,
|
|||
if (err)
|
||||
goto acc_spi_read_error;
|
||||
|
||||
memcpy(data, tb->rx_buf, len*sizeof(u8));
|
||||
memcpy(data, tb->rx_buf, len);
|
||||
mutex_unlock(&tb->buf_lock);
|
||||
return len;
|
||||
|
||||
|
|
|
@ -445,7 +445,7 @@ static int ad9523_store_eeprom(struct iio_dev *indio_dev)
|
|||
|
||||
tmp = 4;
|
||||
do {
|
||||
msleep(16);
|
||||
msleep(20);
|
||||
ret = ad9523_read(indio_dev,
|
||||
AD9523_EEPROM_DATA_XFER_STATUS);
|
||||
if (ret < 0)
|
||||
|
|
|
@ -387,10 +387,8 @@ static struct adf4350_platform_data *adf4350_parse_dt(struct device *dev)
|
|||
int ret;
|
||||
|
||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
dev_err(dev, "could not allocate memory for platform data\n");
|
||||
if (!pdata)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strncpy(&pdata->name[0], np->name, SPI_NAME_SIZE - 1);
|
||||
|
||||
|
@ -613,9 +611,8 @@ static int adf4350_remove(struct spi_device *spi)
|
|||
if (st->clk)
|
||||
clk_disable_unprepare(st->clk);
|
||||
|
||||
if (!IS_ERR(reg)) {
|
||||
if (!IS_ERR(reg))
|
||||
regulator_disable(reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ itg3200-y := itg3200_core.o
|
|||
itg3200-$(CONFIG_IIO_BUFFER) += itg3200_buffer.o
|
||||
obj-$(CONFIG_ITG3200) += itg3200.o
|
||||
|
||||
obj-$(CONFIG_IIO_SSP_SENSORS_COMMONS) += ssp_gyro_sensor.o
|
||||
|
||||
obj-$(CONFIG_IIO_ST_GYRO_3AXIS) += st_gyro.o
|
||||
st_gyro-y := st_gyro_core.o
|
||||
st_gyro-$(CONFIG_IIO_BUFFER) += st_gyro_buffer.o
|
||||
|
|
|
@ -111,19 +111,12 @@ static int gyro_3d_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case 0:
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&gyro_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&gyro_state->common_attributes, true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
report_id = gyro_state->gyro[chan->scan_index].report_id;
|
||||
address = gyro_3d_addresses[chan->scan_index];
|
||||
if (report_id >= 0)
|
||||
|
@ -416,6 +409,7 @@ static struct platform_driver hid_gyro_3d_platform_driver = {
|
|||
.id_table = hid_gyro_3d_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_gyro_3d_probe,
|
||||
.remove = hid_gyro_3d_remove,
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/iio/common/ssp_sensors.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/kfifo_buf.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../common/ssp_sensors/ssp_iio_sensor.h"
|
||||
|
||||
#define SSP_CHANNEL_COUNT 3
|
||||
|
||||
#define SSP_GYROSCOPE_NAME "ssp-gyroscope"
|
||||
static const char ssp_gyro_name[] = SSP_GYROSCOPE_NAME;
|
||||
|
||||
enum ssp_gyro_3d_channel {
|
||||
SSP_CHANNEL_SCAN_INDEX_X,
|
||||
SSP_CHANNEL_SCAN_INDEX_Y,
|
||||
SSP_CHANNEL_SCAN_INDEX_Z,
|
||||
SSP_CHANNEL_SCAN_INDEX_TIME,
|
||||
};
|
||||
|
||||
static int ssp_gyro_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int *val,
|
||||
int *val2, long mask)
|
||||
{
|
||||
u32 t;
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
t = ssp_get_sensor_delay(data, SSP_GYROSCOPE_SENSOR);
|
||||
ssp_convert_to_freq(t, val, val2);
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int ssp_gyro_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan, int val,
|
||||
int val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct ssp_data *data = dev_get_drvdata(indio_dev->dev.parent->parent);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
ret = ssp_convert_to_time(val, val2);
|
||||
ret = ssp_change_delay(data, SSP_GYROSCOPE_SENSOR, ret);
|
||||
if (ret < 0)
|
||||
dev_err(&indio_dev->dev, "gyro sensor enable fail\n");
|
||||
|
||||
return ret;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct iio_info ssp_gyro_iio_info = {
|
||||
.read_raw = &ssp_gyro_read_raw,
|
||||
.write_raw = &ssp_gyro_write_raw,
|
||||
};
|
||||
|
||||
static const unsigned long ssp_gyro_scan_mask[] = { 0x07, 0, };
|
||||
|
||||
static const struct iio_chan_spec ssp_gyro_channels[] = {
|
||||
SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_X, SSP_CHANNEL_SCAN_INDEX_X),
|
||||
SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Y, SSP_CHANNEL_SCAN_INDEX_Y),
|
||||
SSP_CHANNEL_AG(IIO_ANGL_VEL, IIO_MOD_Z, SSP_CHANNEL_SCAN_INDEX_Z),
|
||||
SSP_CHAN_TIMESTAMP(SSP_CHANNEL_SCAN_INDEX_TIME),
|
||||
};
|
||||
|
||||
static int ssp_process_gyro_data(struct iio_dev *indio_dev, void *buf,
|
||||
int64_t timestamp)
|
||||
{
|
||||
return ssp_common_process_data(indio_dev, buf, SSP_GYROSCOPE_SIZE,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops ssp_gyro_buffer_ops = {
|
||||
.postenable = &ssp_common_buffer_postenable,
|
||||
.postdisable = &ssp_common_buffer_postdisable,
|
||||
};
|
||||
|
||||
static int ssp_gyro_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct ssp_sensor_data *spd;
|
||||
struct iio_buffer *buffer;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*spd));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
spd = iio_priv(indio_dev);
|
||||
|
||||
spd->process_data = ssp_process_gyro_data;
|
||||
spd->type = SSP_GYROSCOPE_SENSOR;
|
||||
|
||||
indio_dev->name = ssp_gyro_name;
|
||||
indio_dev->dev.parent = &pdev->dev;
|
||||
indio_dev->info = &ssp_gyro_iio_info;
|
||||
indio_dev->modes = INDIO_BUFFER_SOFTWARE;
|
||||
indio_dev->channels = ssp_gyro_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ssp_gyro_channels);
|
||||
indio_dev->available_scan_masks = ssp_gyro_scan_mask;
|
||||
|
||||
buffer = devm_iio_kfifo_allocate(&pdev->dev);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
iio_device_attach_buffer(indio_dev, buffer);
|
||||
|
||||
indio_dev->setup_ops = &ssp_gyro_buffer_ops;
|
||||
|
||||
platform_set_drvdata(pdev, indio_dev);
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* ssp registering should be done after all iio setup */
|
||||
ssp_register_consumer(indio_dev, SSP_GYROSCOPE_SENSOR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssp_gyro_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ssp_gyro_driver = {
|
||||
.driver = {
|
||||
.name = SSP_GYROSCOPE_NAME,
|
||||
},
|
||||
.probe = ssp_gyro_probe,
|
||||
.remove = ssp_gyro_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ssp_gyro_driver);
|
||||
|
||||
MODULE_AUTHOR("Karol Wrona <k.wrona@samsung.com>");
|
||||
MODULE_DESCRIPTION("Samsung sensorhub gyroscopes driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -48,6 +48,8 @@ unsigned int iio_buffer_poll(struct file *filp,
|
|||
ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf,
|
||||
size_t n, loff_t *f_ps);
|
||||
|
||||
int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev);
|
||||
void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev);
|
||||
|
||||
#define iio_buffer_poll_addr (&iio_buffer_poll)
|
||||
#define iio_buffer_read_first_n_outer_addr (&iio_buffer_read_first_n_outer)
|
||||
|
@ -60,6 +62,13 @@ void iio_buffer_wakeup_poll(struct iio_dev *indio_dev);
|
|||
#define iio_buffer_poll_addr NULL
|
||||
#define iio_buffer_read_first_n_outer_addr NULL
|
||||
|
||||
static inline int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev) {}
|
||||
|
||||
static inline void iio_disable_all_buffers(struct iio_dev *indio_dev) {}
|
||||
static inline void iio_buffer_wakeup_poll(struct iio_dev *indio_dev) {}
|
||||
|
||||
|
|
|
@ -25,6 +25,17 @@ config ADIS16480
|
|||
Say yes here to build support for Analog Devices ADIS16375, ADIS16480,
|
||||
ADIS16485, ADIS16488 inertial sensors.
|
||||
|
||||
config KMX61
|
||||
tristate "Kionix KMX61 6-axis accelerometer and magnetometer"
|
||||
depends on I2C
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
help
|
||||
Say Y here if you want to build a driver for Kionix KMX61 6-axis
|
||||
accelerometer and magnetometer.
|
||||
To compile this driver as module, choose M here: the module will
|
||||
be called kmx61.
|
||||
|
||||
source "drivers/iio/imu/inv_mpu6050/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -14,3 +14,5 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o
|
|||
obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o
|
||||
|
||||
obj-y += inv_mpu6050/
|
||||
|
||||
obj-$(CONFIG_KMX61) += kmx61.o
|
||||
|
|
|
@ -7,6 +7,7 @@ config INV_MPU6050_IIO
|
|||
depends on I2C && SYSFS
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select I2C_MUX
|
||||
help
|
||||
This driver supports the Invensense MPU6050 devices.
|
||||
This driver can also support MPU6500 in MPU6050 compatibility mode
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include <linux/kfifo.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/i2c-mux.h>
|
||||
#include <linux/acpi.h>
|
||||
#include "inv_mpu_iio.h"
|
||||
|
||||
/*
|
||||
|
@ -52,6 +54,7 @@ static const struct inv_mpu6050_reg_map reg_set_6050 = {
|
|||
.int_enable = INV_MPU6050_REG_INT_ENABLE,
|
||||
.pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1,
|
||||
.pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2,
|
||||
.int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG,
|
||||
};
|
||||
|
||||
static const struct inv_mpu6050_chip_config chip_config_6050 = {
|
||||
|
@ -77,6 +80,83 @@ int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 d)
|
|||
return i2c_smbus_write_i2c_block_data(st->client, reg, 1, &d);
|
||||
}
|
||||
|
||||
/*
|
||||
* The i2c read/write needs to happen in unlocked mode. As the parent
|
||||
* adapter is common. If we use locked versions, it will fail as
|
||||
* the mux adapter will lock the parent i2c adapter, while calling
|
||||
* select/deselect functions.
|
||||
*/
|
||||
static int inv_mpu6050_write_reg_unlocked(struct inv_mpu6050_state *st,
|
||||
u8 reg, u8 d)
|
||||
{
|
||||
int ret;
|
||||
u8 buf[2];
|
||||
struct i2c_msg msg[1] = {
|
||||
{
|
||||
.addr = st->client->addr,
|
||||
.flags = 0,
|
||||
.len = sizeof(buf),
|
||||
.buf = buf,
|
||||
}
|
||||
};
|
||||
|
||||
buf[0] = reg;
|
||||
buf[1] = d;
|
||||
ret = __i2c_transfer(st->client->adapter, msg, 1);
|
||||
if (ret != 1)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int inv_mpu6050_select_bypass(struct i2c_adapter *adap, void *mux_priv,
|
||||
u32 chan_id)
|
||||
{
|
||||
struct iio_dev *indio_dev = mux_priv;
|
||||
struct inv_mpu6050_state *st = iio_priv(indio_dev);
|
||||
int ret = 0;
|
||||
|
||||
/* Use the same mutex which was used everywhere to protect power-op */
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
if (!st->powerup_count) {
|
||||
ret = inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1,
|
||||
0);
|
||||
if (ret)
|
||||
goto write_error;
|
||||
|
||||
msleep(INV_MPU6050_REG_UP_TIME);
|
||||
}
|
||||
if (!ret) {
|
||||
st->powerup_count++;
|
||||
ret = inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg,
|
||||
st->client->irq |
|
||||
INV_MPU6050_BIT_BYPASS_EN);
|
||||
}
|
||||
write_error:
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int inv_mpu6050_deselect_bypass(struct i2c_adapter *adap,
|
||||
void *mux_priv, u32 chan_id)
|
||||
{
|
||||
struct iio_dev *indio_dev = mux_priv;
|
||||
struct inv_mpu6050_state *st = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
/* It doesn't really mattter, if any of the calls fails */
|
||||
inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg,
|
||||
st->client->irq);
|
||||
st->powerup_count--;
|
||||
if (!st->powerup_count)
|
||||
inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1,
|
||||
INV_MPU6050_BIT_SLEEP);
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask)
|
||||
{
|
||||
u8 d, mgmt_1;
|
||||
|
@ -133,13 +213,22 @@ int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask)
|
|||
|
||||
int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on)
|
||||
{
|
||||
int result;
|
||||
int result = 0;
|
||||
|
||||
if (power_on) {
|
||||
/* Already under indio-dev->mlock mutex */
|
||||
if (!st->powerup_count)
|
||||
result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1,
|
||||
0);
|
||||
if (!result)
|
||||
st->powerup_count++;
|
||||
} else {
|
||||
st->powerup_count--;
|
||||
if (!st->powerup_count)
|
||||
result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1,
|
||||
INV_MPU6050_BIT_SLEEP);
|
||||
}
|
||||
|
||||
if (power_on)
|
||||
result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, 0);
|
||||
else
|
||||
result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1,
|
||||
INV_MPU6050_BIT_SLEEP);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
|
@ -673,6 +762,7 @@ static int inv_mpu_probe(struct i2c_client *client,
|
|||
|
||||
st = iio_priv(indio_dev);
|
||||
st->client = client;
|
||||
st->powerup_count = 0;
|
||||
pdata = dev_get_platdata(&client->dev);
|
||||
if (pdata)
|
||||
st->plat_data = *pdata;
|
||||
|
@ -720,8 +810,21 @@ static int inv_mpu_probe(struct i2c_client *client,
|
|||
goto out_remove_trigger;
|
||||
}
|
||||
|
||||
st->mux_adapter = i2c_add_mux_adapter(client->adapter,
|
||||
&client->dev,
|
||||
indio_dev,
|
||||
0, 0, 0,
|
||||
inv_mpu6050_select_bypass,
|
||||
inv_mpu6050_deselect_bypass);
|
||||
if (!st->mux_adapter) {
|
||||
result = -ENODEV;
|
||||
goto out_unreg_device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_unreg_device:
|
||||
iio_device_unregister(indio_dev);
|
||||
out_remove_trigger:
|
||||
inv_mpu6050_remove_trigger(st);
|
||||
out_unreg_ring:
|
||||
|
@ -734,6 +837,7 @@ static int inv_mpu_remove(struct i2c_client *client)
|
|||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct inv_mpu6050_state *st = iio_priv(indio_dev);
|
||||
|
||||
i2c_del_mux_adapter(st->mux_adapter);
|
||||
iio_device_unregister(indio_dev);
|
||||
inv_mpu6050_remove_trigger(st);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
|
@ -772,6 +876,13 @@ static const struct i2c_device_id inv_mpu_id[] = {
|
|||
|
||||
MODULE_DEVICE_TABLE(i2c, inv_mpu_id);
|
||||
|
||||
static const struct acpi_device_id inv_acpi_match[] = {
|
||||
{"INVN6500", 0},
|
||||
{ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(acpi, inv_acpi_match);
|
||||
|
||||
static struct i2c_driver inv_mpu_driver = {
|
||||
.probe = inv_mpu_probe,
|
||||
.remove = inv_mpu_remove,
|
||||
|
@ -780,6 +891,7 @@ static struct i2c_driver inv_mpu_driver = {
|
|||
.owner = THIS_MODULE,
|
||||
.name = "inv-mpu6050",
|
||||
.pm = INV_MPU6050_PMOPS,
|
||||
.acpi_match_table = ACPI_PTR(inv_acpi_match),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ struct inv_mpu6050_reg_map {
|
|||
u8 int_enable;
|
||||
u8 pwr_mgmt_1;
|
||||
u8 pwr_mgmt_2;
|
||||
u8 int_pin_cfg;
|
||||
};
|
||||
|
||||
/*device enum */
|
||||
|
@ -119,6 +120,8 @@ struct inv_mpu6050_state {
|
|||
enum inv_devices chip_type;
|
||||
spinlock_t time_stamp_lock;
|
||||
struct i2c_client *client;
|
||||
struct i2c_adapter *mux_adapter;
|
||||
unsigned int powerup_count;
|
||||
struct inv_mpu6050_platform_data plat_data;
|
||||
DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE);
|
||||
};
|
||||
|
@ -179,6 +182,9 @@ struct inv_mpu6050_state {
|
|||
/* 6 + 6 round up and plus 8 */
|
||||
#define INV_MPU6050_OUTPUT_DATA_SIZE 24
|
||||
|
||||
#define INV_MPU6050_REG_INT_PIN_CFG 0x37
|
||||
#define INV_MPU6050_BIT_BYPASS_EN 0x2
|
||||
|
||||
/* init parameters */
|
||||
#define INV_MPU6050_INIT_FIFO_RATE 50
|
||||
#define INV_MPU6050_TIME_STAMP_TOR 5
|
||||
|
|
|
@ -116,40 +116,35 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev)
|
|||
int ret;
|
||||
struct inv_mpu6050_state *st = iio_priv(indio_dev);
|
||||
|
||||
st->trig = iio_trigger_alloc("%s-dev%d",
|
||||
indio_dev->name,
|
||||
indio_dev->id);
|
||||
if (st->trig == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_ret;
|
||||
}
|
||||
ret = request_irq(st->client->irq, &iio_trigger_generic_data_rdy_poll,
|
||||
IRQF_TRIGGER_RISING,
|
||||
"inv_mpu",
|
||||
st->trig);
|
||||
st->trig = devm_iio_trigger_alloc(&indio_dev->dev,
|
||||
"%s-dev%d",
|
||||
indio_dev->name,
|
||||
indio_dev->id);
|
||||
if (!st->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_request_irq(&indio_dev->dev, st->client->irq,
|
||||
&iio_trigger_generic_data_rdy_poll,
|
||||
IRQF_TRIGGER_RISING,
|
||||
"inv_mpu",
|
||||
st->trig);
|
||||
if (ret)
|
||||
goto error_free_trig;
|
||||
return ret;
|
||||
|
||||
st->trig->dev.parent = &st->client->dev;
|
||||
st->trig->ops = &inv_mpu_trigger_ops;
|
||||
iio_trigger_set_drvdata(st->trig, indio_dev);
|
||||
|
||||
ret = iio_trigger_register(st->trig);
|
||||
if (ret)
|
||||
goto error_free_irq;
|
||||
return ret;
|
||||
|
||||
indio_dev->trig = iio_trigger_get(st->trig);
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_irq:
|
||||
free_irq(st->client->irq, st->trig);
|
||||
error_free_trig:
|
||||
iio_trigger_free(st->trig);
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void inv_mpu6050_remove_trigger(struct inv_mpu6050_state *st)
|
||||
{
|
||||
iio_trigger_unregister(st->trig);
|
||||
free_irq(st->client->irq, st->trig);
|
||||
iio_trigger_free(st->trig);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -178,6 +178,80 @@ static ssize_t iio_scan_el_show(struct device *dev,
|
|||
return sprintf(buf, "%d\n", ret);
|
||||
}
|
||||
|
||||
/* Note NULL used as error indicator as it doesn't make sense. */
|
||||
static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks,
|
||||
unsigned int masklength,
|
||||
const unsigned long *mask)
|
||||
{
|
||||
if (bitmap_empty(mask, masklength))
|
||||
return NULL;
|
||||
while (*av_masks) {
|
||||
if (bitmap_subset(mask, av_masks, masklength))
|
||||
return av_masks;
|
||||
av_masks += BITS_TO_LONGS(masklength);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool iio_validate_scan_mask(struct iio_dev *indio_dev,
|
||||
const unsigned long *mask)
|
||||
{
|
||||
if (!indio_dev->setup_ops->validate_scan_mask)
|
||||
return true;
|
||||
|
||||
return indio_dev->setup_ops->validate_scan_mask(indio_dev, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_scan_mask_set() - set particular bit in the scan mask
|
||||
* @indio_dev: the iio device
|
||||
* @buffer: the buffer whose scan mask we are interested in
|
||||
* @bit: the bit to be set.
|
||||
*
|
||||
* Note that at this point we have no way of knowing what other
|
||||
* buffers might request, hence this code only verifies that the
|
||||
* individual buffers request is plausible.
|
||||
*/
|
||||
static int iio_scan_mask_set(struct iio_dev *indio_dev,
|
||||
struct iio_buffer *buffer, int bit)
|
||||
{
|
||||
const unsigned long *mask;
|
||||
unsigned long *trialmask;
|
||||
|
||||
trialmask = kmalloc(sizeof(*trialmask)*
|
||||
BITS_TO_LONGS(indio_dev->masklength),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (trialmask == NULL)
|
||||
return -ENOMEM;
|
||||
if (!indio_dev->masklength) {
|
||||
WARN_ON("Trying to set scanmask prior to registering buffer\n");
|
||||
goto err_invalid_mask;
|
||||
}
|
||||
bitmap_copy(trialmask, buffer->scan_mask, indio_dev->masklength);
|
||||
set_bit(bit, trialmask);
|
||||
|
||||
if (!iio_validate_scan_mask(indio_dev, trialmask))
|
||||
goto err_invalid_mask;
|
||||
|
||||
if (indio_dev->available_scan_masks) {
|
||||
mask = iio_scan_mask_match(indio_dev->available_scan_masks,
|
||||
indio_dev->masklength,
|
||||
trialmask);
|
||||
if (!mask)
|
||||
goto err_invalid_mask;
|
||||
}
|
||||
bitmap_copy(buffer->scan_mask, trialmask, indio_dev->masklength);
|
||||
|
||||
kfree(trialmask);
|
||||
|
||||
return 0;
|
||||
|
||||
err_invalid_mask:
|
||||
kfree(trialmask);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int iio_scan_mask_clear(struct iio_buffer *buffer, int bit)
|
||||
{
|
||||
clear_bit(bit, buffer->scan_mask);
|
||||
|
@ -309,115 +383,19 @@ static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static const char * const iio_scan_elements_group_name = "scan_elements";
|
||||
|
||||
int iio_buffer_register(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *channels,
|
||||
int num_channels)
|
||||
{
|
||||
struct iio_dev_attr *p;
|
||||
struct attribute **attr;
|
||||
struct iio_buffer *buffer = indio_dev->buffer;
|
||||
int ret, i, attrn, attrcount, attrcount_orig = 0;
|
||||
|
||||
if (buffer->attrs)
|
||||
indio_dev->groups[indio_dev->groupcounter++] = buffer->attrs;
|
||||
|
||||
if (buffer->scan_el_attrs != NULL) {
|
||||
attr = buffer->scan_el_attrs->attrs;
|
||||
while (*attr++ != NULL)
|
||||
attrcount_orig++;
|
||||
}
|
||||
attrcount = attrcount_orig;
|
||||
INIT_LIST_HEAD(&buffer->scan_el_dev_attr_list);
|
||||
if (channels) {
|
||||
/* new magic */
|
||||
for (i = 0; i < num_channels; i++) {
|
||||
if (channels[i].scan_index < 0)
|
||||
continue;
|
||||
|
||||
/* Establish necessary mask length */
|
||||
if (channels[i].scan_index >
|
||||
(int)indio_dev->masklength - 1)
|
||||
indio_dev->masklength
|
||||
= channels[i].scan_index + 1;
|
||||
|
||||
ret = iio_buffer_add_channel_sysfs(indio_dev,
|
||||
&channels[i]);
|
||||
if (ret < 0)
|
||||
goto error_cleanup_dynamic;
|
||||
attrcount += ret;
|
||||
if (channels[i].type == IIO_TIMESTAMP)
|
||||
indio_dev->scan_index_timestamp =
|
||||
channels[i].scan_index;
|
||||
}
|
||||
if (indio_dev->masklength && buffer->scan_mask == NULL) {
|
||||
buffer->scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
|
||||
sizeof(*buffer->scan_mask),
|
||||
GFP_KERNEL);
|
||||
if (buffer->scan_mask == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_cleanup_dynamic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer->scan_el_group.name = iio_scan_elements_group_name;
|
||||
|
||||
buffer->scan_el_group.attrs = kcalloc(attrcount + 1,
|
||||
sizeof(buffer->scan_el_group.attrs[0]),
|
||||
GFP_KERNEL);
|
||||
if (buffer->scan_el_group.attrs == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_free_scan_mask;
|
||||
}
|
||||
if (buffer->scan_el_attrs)
|
||||
memcpy(buffer->scan_el_group.attrs, buffer->scan_el_attrs,
|
||||
sizeof(buffer->scan_el_group.attrs[0])*attrcount_orig);
|
||||
attrn = attrcount_orig;
|
||||
|
||||
list_for_each_entry(p, &buffer->scan_el_dev_attr_list, l)
|
||||
buffer->scan_el_group.attrs[attrn++] = &p->dev_attr.attr;
|
||||
indio_dev->groups[indio_dev->groupcounter++] = &buffer->scan_el_group;
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_scan_mask:
|
||||
kfree(buffer->scan_mask);
|
||||
error_cleanup_dynamic:
|
||||
iio_free_chan_devattr_list(&buffer->scan_el_dev_attr_list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_register);
|
||||
|
||||
void iio_buffer_unregister(struct iio_dev *indio_dev)
|
||||
{
|
||||
kfree(indio_dev->buffer->scan_mask);
|
||||
kfree(indio_dev->buffer->scan_el_group.attrs);
|
||||
iio_free_chan_devattr_list(&indio_dev->buffer->scan_el_dev_attr_list);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_unregister);
|
||||
|
||||
ssize_t iio_buffer_read_length(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
static ssize_t iio_buffer_read_length(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct iio_buffer *buffer = indio_dev->buffer;
|
||||
|
||||
if (buffer->access->get_length)
|
||||
return sprintf(buf, "%d\n",
|
||||
buffer->access->get_length(buffer));
|
||||
|
||||
return 0;
|
||||
return sprintf(buf, "%d\n", buffer->length);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_read_length);
|
||||
|
||||
ssize_t iio_buffer_write_length(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
static ssize_t iio_buffer_write_length(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
struct iio_buffer *buffer = indio_dev->buffer;
|
||||
|
@ -428,47 +406,28 @@ ssize_t iio_buffer_write_length(struct device *dev,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (buffer->access->get_length)
|
||||
if (val == buffer->access->get_length(buffer))
|
||||
return len;
|
||||
if (val == buffer->length)
|
||||
return len;
|
||||
|
||||
mutex_lock(&indio_dev->mlock);
|
||||
if (iio_buffer_is_active(indio_dev->buffer)) {
|
||||
ret = -EBUSY;
|
||||
} else {
|
||||
if (buffer->access->set_length)
|
||||
buffer->access->set_length(buffer, val);
|
||||
buffer->access->set_length(buffer, val);
|
||||
ret = 0;
|
||||
}
|
||||
mutex_unlock(&indio_dev->mlock);
|
||||
|
||||
return ret ? ret : len;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_write_length);
|
||||
|
||||
ssize_t iio_buffer_show_enable(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
static ssize_t iio_buffer_show_enable(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
|
||||
return sprintf(buf, "%d\n", iio_buffer_is_active(indio_dev->buffer));
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_show_enable);
|
||||
|
||||
/* Note NULL used as error indicator as it doesn't make sense. */
|
||||
static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks,
|
||||
unsigned int masklength,
|
||||
const unsigned long *mask)
|
||||
{
|
||||
if (bitmap_empty(mask, masklength))
|
||||
return NULL;
|
||||
while (*av_masks) {
|
||||
if (bitmap_subset(mask, av_masks, masklength))
|
||||
return av_masks;
|
||||
av_masks += BITS_TO_LONGS(masklength);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int iio_compute_scan_bytes(struct iio_dev *indio_dev,
|
||||
const unsigned long *mask, bool timestamp)
|
||||
|
@ -680,6 +639,8 @@ static int __iio_update_buffers(struct iio_dev *indio_dev,
|
|||
indio_dev->currentmode = INDIO_BUFFER_TRIGGERED;
|
||||
} else if (indio_dev->modes & INDIO_BUFFER_HARDWARE) {
|
||||
indio_dev->currentmode = INDIO_BUFFER_HARDWARE;
|
||||
} else if (indio_dev->modes & INDIO_BUFFER_SOFTWARE) {
|
||||
indio_dev->currentmode = INDIO_BUFFER_SOFTWARE;
|
||||
} else { /* Should never be reached */
|
||||
ret = -EINVAL;
|
||||
goto error_run_postdisable;
|
||||
|
@ -755,10 +716,10 @@ int iio_update_buffers(struct iio_dev *indio_dev,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(iio_update_buffers);
|
||||
|
||||
ssize_t iio_buffer_store_enable(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
static ssize_t iio_buffer_store_enable(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
int ret;
|
||||
bool requested_state;
|
||||
|
@ -790,7 +751,130 @@ ssize_t iio_buffer_store_enable(struct device *dev,
|
|||
mutex_unlock(&indio_dev->mlock);
|
||||
return (ret < 0) ? ret : len;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_buffer_store_enable);
|
||||
|
||||
static const char * const iio_scan_elements_group_name = "scan_elements";
|
||||
|
||||
static DEVICE_ATTR(length, S_IRUGO | S_IWUSR, iio_buffer_read_length,
|
||||
iio_buffer_write_length);
|
||||
static struct device_attribute dev_attr_length_ro = __ATTR(length,
|
||||
S_IRUGO, iio_buffer_read_length, NULL);
|
||||
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR,
|
||||
iio_buffer_show_enable, iio_buffer_store_enable);
|
||||
|
||||
int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct iio_dev_attr *p;
|
||||
struct attribute **attr;
|
||||
struct iio_buffer *buffer = indio_dev->buffer;
|
||||
int ret, i, attrn, attrcount, attrcount_orig = 0;
|
||||
const struct iio_chan_spec *channels;
|
||||
|
||||
if (!buffer)
|
||||
return 0;
|
||||
|
||||
attrcount = 0;
|
||||
if (buffer->attrs) {
|
||||
while (buffer->attrs[attrcount] != NULL)
|
||||
attrcount++;
|
||||
}
|
||||
|
||||
buffer->buffer_group.name = "buffer";
|
||||
buffer->buffer_group.attrs = kcalloc(attrcount + 3,
|
||||
sizeof(*buffer->buffer_group.attrs), GFP_KERNEL);
|
||||
if (!buffer->buffer_group.attrs)
|
||||
return -ENOMEM;
|
||||
|
||||
if (buffer->access->set_length)
|
||||
buffer->buffer_group.attrs[0] = &dev_attr_length.attr;
|
||||
else
|
||||
buffer->buffer_group.attrs[0] = &dev_attr_length_ro.attr;
|
||||
buffer->buffer_group.attrs[1] = &dev_attr_enable.attr;
|
||||
if (buffer->attrs)
|
||||
memcpy(&buffer->buffer_group.attrs[2], buffer->attrs,
|
||||
sizeof(*&buffer->buffer_group.attrs) * attrcount);
|
||||
buffer->buffer_group.attrs[attrcount+2] = NULL;
|
||||
|
||||
indio_dev->groups[indio_dev->groupcounter++] = &buffer->buffer_group;
|
||||
|
||||
if (buffer->scan_el_attrs != NULL) {
|
||||
attr = buffer->scan_el_attrs->attrs;
|
||||
while (*attr++ != NULL)
|
||||
attrcount_orig++;
|
||||
}
|
||||
attrcount = attrcount_orig;
|
||||
INIT_LIST_HEAD(&buffer->scan_el_dev_attr_list);
|
||||
channels = indio_dev->channels;
|
||||
if (channels) {
|
||||
/* new magic */
|
||||
for (i = 0; i < indio_dev->num_channels; i++) {
|
||||
if (channels[i].scan_index < 0)
|
||||
continue;
|
||||
|
||||
/* Establish necessary mask length */
|
||||
if (channels[i].scan_index >
|
||||
(int)indio_dev->masklength - 1)
|
||||
indio_dev->masklength
|
||||
= channels[i].scan_index + 1;
|
||||
|
||||
ret = iio_buffer_add_channel_sysfs(indio_dev,
|
||||
&channels[i]);
|
||||
if (ret < 0)
|
||||
goto error_cleanup_dynamic;
|
||||
attrcount += ret;
|
||||
if (channels[i].type == IIO_TIMESTAMP)
|
||||
indio_dev->scan_index_timestamp =
|
||||
channels[i].scan_index;
|
||||
}
|
||||
if (indio_dev->masklength && buffer->scan_mask == NULL) {
|
||||
buffer->scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength),
|
||||
sizeof(*buffer->scan_mask),
|
||||
GFP_KERNEL);
|
||||
if (buffer->scan_mask == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_cleanup_dynamic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer->scan_el_group.name = iio_scan_elements_group_name;
|
||||
|
||||
buffer->scan_el_group.attrs = kcalloc(attrcount + 1,
|
||||
sizeof(buffer->scan_el_group.attrs[0]),
|
||||
GFP_KERNEL);
|
||||
if (buffer->scan_el_group.attrs == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_free_scan_mask;
|
||||
}
|
||||
if (buffer->scan_el_attrs)
|
||||
memcpy(buffer->scan_el_group.attrs, buffer->scan_el_attrs,
|
||||
sizeof(buffer->scan_el_group.attrs[0])*attrcount_orig);
|
||||
attrn = attrcount_orig;
|
||||
|
||||
list_for_each_entry(p, &buffer->scan_el_dev_attr_list, l)
|
||||
buffer->scan_el_group.attrs[attrn++] = &p->dev_attr.attr;
|
||||
indio_dev->groups[indio_dev->groupcounter++] = &buffer->scan_el_group;
|
||||
|
||||
return 0;
|
||||
|
||||
error_free_scan_mask:
|
||||
kfree(buffer->scan_mask);
|
||||
error_cleanup_dynamic:
|
||||
iio_free_chan_devattr_list(&buffer->scan_el_dev_attr_list);
|
||||
kfree(indio_dev->buffer->buffer_group.attrs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void iio_buffer_free_sysfs_and_mask(struct iio_dev *indio_dev)
|
||||
{
|
||||
if (!indio_dev->buffer)
|
||||
return;
|
||||
|
||||
kfree(indio_dev->buffer->scan_mask);
|
||||
kfree(indio_dev->buffer->buffer_group.attrs);
|
||||
kfree(indio_dev->buffer->scan_el_group.attrs);
|
||||
iio_free_chan_devattr_list(&indio_dev->buffer->scan_el_dev_attr_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_validate_scan_mask_onehot() - Validates that exactly one channel is selected
|
||||
|
@ -808,66 +892,6 @@ bool iio_validate_scan_mask_onehot(struct iio_dev *indio_dev,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(iio_validate_scan_mask_onehot);
|
||||
|
||||
static bool iio_validate_scan_mask(struct iio_dev *indio_dev,
|
||||
const unsigned long *mask)
|
||||
{
|
||||
if (!indio_dev->setup_ops->validate_scan_mask)
|
||||
return true;
|
||||
|
||||
return indio_dev->setup_ops->validate_scan_mask(indio_dev, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* iio_scan_mask_set() - set particular bit in the scan mask
|
||||
* @indio_dev: the iio device
|
||||
* @buffer: the buffer whose scan mask we are interested in
|
||||
* @bit: the bit to be set.
|
||||
*
|
||||
* Note that at this point we have no way of knowing what other
|
||||
* buffers might request, hence this code only verifies that the
|
||||
* individual buffers request is plausible.
|
||||
*/
|
||||
int iio_scan_mask_set(struct iio_dev *indio_dev,
|
||||
struct iio_buffer *buffer, int bit)
|
||||
{
|
||||
const unsigned long *mask;
|
||||
unsigned long *trialmask;
|
||||
|
||||
trialmask = kmalloc(sizeof(*trialmask)*
|
||||
BITS_TO_LONGS(indio_dev->masklength),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (trialmask == NULL)
|
||||
return -ENOMEM;
|
||||
if (!indio_dev->masklength) {
|
||||
WARN_ON("Trying to set scanmask prior to registering buffer\n");
|
||||
goto err_invalid_mask;
|
||||
}
|
||||
bitmap_copy(trialmask, buffer->scan_mask, indio_dev->masklength);
|
||||
set_bit(bit, trialmask);
|
||||
|
||||
if (!iio_validate_scan_mask(indio_dev, trialmask))
|
||||
goto err_invalid_mask;
|
||||
|
||||
if (indio_dev->available_scan_masks) {
|
||||
mask = iio_scan_mask_match(indio_dev->available_scan_masks,
|
||||
indio_dev->masklength,
|
||||
trialmask);
|
||||
if (!mask)
|
||||
goto err_invalid_mask;
|
||||
}
|
||||
bitmap_copy(buffer->scan_mask, trialmask, indio_dev->masklength);
|
||||
|
||||
kfree(trialmask);
|
||||
|
||||
return 0;
|
||||
|
||||
err_invalid_mask:
|
||||
kfree(trialmask);
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_scan_mask_set);
|
||||
|
||||
int iio_scan_mask_query(struct iio_dev *indio_dev,
|
||||
struct iio_buffer *buffer, int bit)
|
||||
{
|
||||
|
|
|
@ -70,6 +70,11 @@ static const char * const iio_chan_type_name_spec[] = {
|
|||
[IIO_CCT] = "cct",
|
||||
[IIO_PRESSURE] = "pressure",
|
||||
[IIO_HUMIDITYRELATIVE] = "humidityrelative",
|
||||
[IIO_ACTIVITY] = "activity",
|
||||
[IIO_STEPS] = "steps",
|
||||
[IIO_ENERGY] = "energy",
|
||||
[IIO_DISTANCE] = "distance",
|
||||
[IIO_VELOCITY] = "velocity",
|
||||
};
|
||||
|
||||
static const char * const iio_modifier_names[] = {
|
||||
|
@ -91,6 +96,11 @@ static const char * const iio_modifier_names[] = {
|
|||
[IIO_MOD_NORTH_TRUE] = "from_north_true",
|
||||
[IIO_MOD_NORTH_MAGN_TILT_COMP] = "from_north_magnetic_tilt_comp",
|
||||
[IIO_MOD_NORTH_TRUE_TILT_COMP] = "from_north_true_tilt_comp",
|
||||
[IIO_MOD_RUNNING] = "running",
|
||||
[IIO_MOD_JOGGING] = "jogging",
|
||||
[IIO_MOD_WALKING] = "walking",
|
||||
[IIO_MOD_STILL] = "still",
|
||||
[IIO_MOD_ROOT_SUM_SQUARED_X_Y_Z] = "sqrt(x^2+y^2+z^2)",
|
||||
};
|
||||
|
||||
/* relies on pairs of these shared then separate */
|
||||
|
@ -113,6 +123,11 @@ static const char * const iio_chan_info_postfix[] = {
|
|||
[IIO_CHAN_INFO_HARDWAREGAIN] = "hardwaregain",
|
||||
[IIO_CHAN_INFO_HYSTERESIS] = "hysteresis",
|
||||
[IIO_CHAN_INFO_INT_TIME] = "integration_time",
|
||||
[IIO_CHAN_INFO_ENABLE] = "en",
|
||||
[IIO_CHAN_INFO_CALIBHEIGHT] = "calibheight",
|
||||
[IIO_CHAN_INFO_CALIBWEIGHT] = "calibweight",
|
||||
[IIO_CHAN_INFO_DEBOUNCE_COUNT] = "debounce_count",
|
||||
[IIO_CHAN_INFO_DEBOUNCE_TIME] = "debounce_time",
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1035,7 +1050,6 @@ struct iio_dev *devm_iio_device_alloc(struct device *dev, int sizeof_priv)
|
|||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
/* use raw alloc_dr for kmalloc caller tracing */
|
||||
iio_dev = iio_device_alloc(sizeof_priv);
|
||||
if (iio_dev) {
|
||||
*ptr = iio_dev;
|
||||
|
@ -1127,6 +1141,29 @@ static const struct file_operations iio_buffer_fileops = {
|
|||
.compat_ioctl = iio_ioctl,
|
||||
};
|
||||
|
||||
static int iio_check_unique_scan_index(struct iio_dev *indio_dev)
|
||||
{
|
||||
int i, j;
|
||||
const struct iio_chan_spec *channels = indio_dev->channels;
|
||||
|
||||
if (!(indio_dev->modes & INDIO_ALL_BUFFER_MODES))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < indio_dev->num_channels - 1; i++) {
|
||||
if (channels[i].scan_index < 0)
|
||||
continue;
|
||||
for (j = i + 1; j < indio_dev->num_channels; j++)
|
||||
if (channels[i].scan_index == channels[j].scan_index) {
|
||||
dev_err(&indio_dev->dev,
|
||||
"Duplicate scan index %d\n",
|
||||
channels[i].scan_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct iio_buffer_setup_ops noop_ring_setup_ops;
|
||||
|
||||
/**
|
||||
|
@ -1141,6 +1178,10 @@ int iio_device_register(struct iio_dev *indio_dev)
|
|||
if (!indio_dev->dev.of_node && indio_dev->dev.parent)
|
||||
indio_dev->dev.of_node = indio_dev->dev.parent->of_node;
|
||||
|
||||
ret = iio_check_unique_scan_index(indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* configure elements for the chrdev */
|
||||
indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id);
|
||||
|
||||
|
@ -1150,11 +1191,19 @@ int iio_device_register(struct iio_dev *indio_dev)
|
|||
"Failed to register debugfs interfaces\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_buffer_alloc_sysfs_and_mask(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"Failed to create buffer sysfs interfaces\n");
|
||||
goto error_unreg_debugfs;
|
||||
}
|
||||
|
||||
ret = iio_device_register_sysfs(indio_dev);
|
||||
if (ret) {
|
||||
dev_err(indio_dev->dev.parent,
|
||||
"Failed to register sysfs interfaces\n");
|
||||
goto error_unreg_debugfs;
|
||||
goto error_buffer_free_sysfs;
|
||||
}
|
||||
ret = iio_device_register_eventset(indio_dev);
|
||||
if (ret) {
|
||||
|
@ -1187,6 +1236,8 @@ int iio_device_register(struct iio_dev *indio_dev)
|
|||
iio_device_unregister_eventset(indio_dev);
|
||||
error_free_sysfs:
|
||||
iio_device_unregister_sysfs(indio_dev);
|
||||
error_buffer_free_sysfs:
|
||||
iio_buffer_free_sysfs_and_mask(indio_dev);
|
||||
error_unreg_debugfs:
|
||||
iio_device_unregister_debugfs(indio_dev);
|
||||
return ret;
|
||||
|
@ -1215,6 +1266,8 @@ void iio_device_unregister(struct iio_dev *indio_dev)
|
|||
iio_buffer_wakeup_poll(indio_dev);
|
||||
|
||||
mutex_unlock(&indio_dev->info_exist_lock);
|
||||
|
||||
iio_buffer_free_sysfs_and_mask(indio_dev);
|
||||
}
|
||||
EXPORT_SYMBOL(iio_device_unregister);
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ static const char * const iio_ev_type_text[] = {
|
|||
[IIO_EV_TYPE_ROC] = "roc",
|
||||
[IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive",
|
||||
[IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive",
|
||||
[IIO_EV_TYPE_CHANGE] = "change",
|
||||
};
|
||||
|
||||
static const char * const iio_ev_dir_text[] = {
|
||||
|
@ -327,9 +328,15 @@ static int iio_device_add_event(struct iio_dev *indio_dev,
|
|||
for_each_set_bit(i, mask, sizeof(*mask)*8) {
|
||||
if (i >= ARRAY_SIZE(iio_ev_info_text))
|
||||
return -EINVAL;
|
||||
postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
|
||||
iio_ev_type_text[type], iio_ev_dir_text[dir],
|
||||
iio_ev_info_text[i]);
|
||||
if (dir != IIO_EV_DIR_NONE)
|
||||
postfix = kasprintf(GFP_KERNEL, "%s_%s_%s",
|
||||
iio_ev_type_text[type],
|
||||
iio_ev_dir_text[dir],
|
||||
iio_ev_info_text[i]);
|
||||
else
|
||||
postfix = kasprintf(GFP_KERNEL, "%s_%s",
|
||||
iio_ev_type_text[type],
|
||||
iio_ev_info_text[i]);
|
||||
if (postfix == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -404,7 +411,7 @@ static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev)
|
|||
{
|
||||
int j, ret, attrcount = 0;
|
||||
|
||||
/* Dynically created from the channels array */
|
||||
/* Dynamically created from the channels array */
|
||||
for (j = 0; j < indio_dev->num_channels; j++) {
|
||||
ret = iio_device_add_event_sysfs(indio_dev,
|
||||
&indio_dev->channels[j]);
|
||||
|
|
|
@ -32,7 +32,7 @@ static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = {
|
|||
*
|
||||
* This function combines some common tasks which will normally be performed
|
||||
* when setting up a triggered buffer. It will allocate the buffer and the
|
||||
* pollfunc, as well as register the buffer with the IIO core.
|
||||
* pollfunc.
|
||||
*
|
||||
* Before calling this function the indio_dev structure should already be
|
||||
* completely initialized, but not yet registered. In practice this means that
|
||||
|
@ -49,7 +49,7 @@ int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
|
|||
struct iio_buffer *buffer;
|
||||
int ret;
|
||||
|
||||
buffer = iio_kfifo_allocate(indio_dev);
|
||||
buffer = iio_kfifo_allocate();
|
||||
if (!buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto error_ret;
|
||||
|
@ -78,16 +78,8 @@ int iio_triggered_buffer_setup(struct iio_dev *indio_dev,
|
|||
/* Flag that polled ring buffering is possible */
|
||||
indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
|
||||
|
||||
ret = iio_buffer_register(indio_dev,
|
||||
indio_dev->channels,
|
||||
indio_dev->num_channels);
|
||||
if (ret)
|
||||
goto error_dealloc_pollfunc;
|
||||
|
||||
return 0;
|
||||
|
||||
error_dealloc_pollfunc:
|
||||
iio_dealloc_pollfunc(indio_dev->pollfunc);
|
||||
error_kfifo_free:
|
||||
iio_kfifo_free(indio_dev->buffer);
|
||||
error_ret:
|
||||
|
@ -101,7 +93,6 @@ EXPORT_SYMBOL(iio_triggered_buffer_setup);
|
|||
*/
|
||||
void iio_triggered_buffer_cleanup(struct iio_dev *indio_dev)
|
||||
{
|
||||
iio_buffer_unregister(indio_dev);
|
||||
iio_dealloc_pollfunc(indio_dev->pollfunc);
|
||||
iio_kfifo_free(indio_dev->buffer);
|
||||
}
|
||||
|
|
|
@ -116,8 +116,11 @@ static int __of_iio_simple_xlate(struct iio_dev *indio_dev,
|
|||
if (!iiospec->args_count)
|
||||
return 0;
|
||||
|
||||
if (iiospec->args[0] >= indio_dev->num_channels)
|
||||
if (iiospec->args[0] >= indio_dev->num_channels) {
|
||||
dev_err(&indio_dev->dev, "invalid channel index %u\n",
|
||||
iiospec->args[0]);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return iiospec->args[0];
|
||||
}
|
||||
|
@ -634,3 +637,28 @@ int iio_get_channel_type(struct iio_channel *chan, enum iio_chan_type *type)
|
|||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_get_channel_type);
|
||||
|
||||
static int iio_channel_write(struct iio_channel *chan, int val, int val2,
|
||||
enum iio_chan_info_enum info)
|
||||
{
|
||||
return chan->indio_dev->info->write_raw(chan->indio_dev,
|
||||
chan->channel, val, val2, info);
|
||||
}
|
||||
|
||||
int iio_write_channel_raw(struct iio_channel *chan, int val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chan->indio_dev->info_exist_lock);
|
||||
if (chan->indio_dev->info == NULL) {
|
||||
ret = -ENODEV;
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
ret = iio_channel_write(chan, val, 0, IIO_CHAN_INFO_RAW);
|
||||
err_unlock:
|
||||
mutex_unlock(&chan->indio_dev->info_exist_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iio_write_channel_raw);
|
||||
|
|
|
@ -47,30 +47,6 @@ static int iio_request_update_kfifo(struct iio_buffer *r)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int iio_get_length_kfifo(struct iio_buffer *r)
|
||||
{
|
||||
return r->length;
|
||||
}
|
||||
|
||||
static IIO_BUFFER_ENABLE_ATTR;
|
||||
static IIO_BUFFER_LENGTH_ATTR;
|
||||
|
||||
static struct attribute *iio_kfifo_attributes[] = {
|
||||
&dev_attr_length.attr,
|
||||
&dev_attr_enable.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group iio_kfifo_attribute_group = {
|
||||
.attrs = iio_kfifo_attributes,
|
||||
.name = "buffer",
|
||||
};
|
||||
|
||||
static int iio_get_bytes_per_datum_kfifo(struct iio_buffer *r)
|
||||
{
|
||||
return r->bytes_per_datum;
|
||||
}
|
||||
|
||||
static int iio_mark_update_needed_kfifo(struct iio_buffer *r)
|
||||
{
|
||||
struct iio_kfifo *kf = iio_to_kfifo(r);
|
||||
|
@ -159,26 +135,25 @@ static const struct iio_buffer_access_funcs kfifo_access_funcs = {
|
|||
.read_first_n = &iio_read_first_n_kfifo,
|
||||
.data_available = iio_kfifo_buf_data_available,
|
||||
.request_update = &iio_request_update_kfifo,
|
||||
.get_bytes_per_datum = &iio_get_bytes_per_datum_kfifo,
|
||||
.set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo,
|
||||
.get_length = &iio_get_length_kfifo,
|
||||
.set_length = &iio_set_length_kfifo,
|
||||
.release = &iio_kfifo_buffer_release,
|
||||
};
|
||||
|
||||
struct iio_buffer *iio_kfifo_allocate(struct iio_dev *indio_dev)
|
||||
struct iio_buffer *iio_kfifo_allocate(void)
|
||||
{
|
||||
struct iio_kfifo *kf;
|
||||
|
||||
kf = kzalloc(sizeof *kf, GFP_KERNEL);
|
||||
kf = kzalloc(sizeof(*kf), GFP_KERNEL);
|
||||
if (!kf)
|
||||
return NULL;
|
||||
|
||||
kf->update_needed = true;
|
||||
iio_buffer_init(&kf->buffer);
|
||||
kf->buffer.attrs = &iio_kfifo_attribute_group;
|
||||
kf->buffer.access = &kfifo_access_funcs;
|
||||
kf->buffer.length = 2;
|
||||
mutex_init(&kf->user_lock);
|
||||
|
||||
return &kf->buffer;
|
||||
}
|
||||
EXPORT_SYMBOL(iio_kfifo_allocate);
|
||||
|
@ -189,4 +164,58 @@ void iio_kfifo_free(struct iio_buffer *r)
|
|||
}
|
||||
EXPORT_SYMBOL(iio_kfifo_free);
|
||||
|
||||
static void devm_iio_kfifo_release(struct device *dev, void *res)
|
||||
{
|
||||
iio_kfifo_free(*(struct iio_buffer **)res);
|
||||
}
|
||||
|
||||
static int devm_iio_kfifo_match(struct device *dev, void *res, void *data)
|
||||
{
|
||||
struct iio_buffer **r = res;
|
||||
|
||||
if (WARN_ON(!r || !*r))
|
||||
return 0;
|
||||
|
||||
return *r == data;
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_iio_fifo_allocate - Resource-managed iio_kfifo_allocate()
|
||||
* @dev: Device to allocate kfifo buffer for
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated iio_buffer on success, NULL on failure.
|
||||
*/
|
||||
struct iio_buffer *devm_iio_kfifo_allocate(struct device *dev)
|
||||
{
|
||||
struct iio_buffer **ptr, *r;
|
||||
|
||||
ptr = devres_alloc(devm_iio_kfifo_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
r = iio_kfifo_allocate();
|
||||
if (r) {
|
||||
*ptr = r;
|
||||
devres_add(dev, ptr);
|
||||
} else {
|
||||
devres_free(ptr);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL(devm_iio_kfifo_allocate);
|
||||
|
||||
/**
|
||||
* devm_iio_fifo_free - Resource-managed iio_kfifo_free()
|
||||
* @dev: Device the buffer belongs to
|
||||
* @r: The buffer associated with the device
|
||||
*/
|
||||
void devm_iio_kfifo_free(struct device *dev, struct iio_buffer *r)
|
||||
{
|
||||
WARN_ON(devres_release(dev, devm_iio_kfifo_release,
|
||||
devm_iio_kfifo_match, r));
|
||||
}
|
||||
EXPORT_SYMBOL(devm_iio_kfifo_free);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -48,6 +48,17 @@ config CM32181
|
|||
To compile this driver as a module, choose M here:
|
||||
the module will be called cm32181.
|
||||
|
||||
config CM3232
|
||||
depends on I2C
|
||||
tristate "CM3232 ambient light sensor"
|
||||
help
|
||||
Say Y here if you use cm3232.
|
||||
This option enables ambient light sensor using
|
||||
Capella Microsystems cm3232 device driver.
|
||||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called cm3232.
|
||||
|
||||
config CM36651
|
||||
depends on I2C
|
||||
tristate "CM36651 driver"
|
||||
|
@ -95,6 +106,9 @@ config HID_SENSOR_ALS
|
|||
Say yes here to build support for the HID SENSOR
|
||||
Ambient light sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-sensor-als.
|
||||
|
||||
config HID_SENSOR_PROX
|
||||
depends on HID_SENSOR_HUB
|
||||
select IIO_BUFFER
|
||||
|
@ -109,6 +123,16 @@ config HID_SENSOR_PROX
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-sensor-prox.
|
||||
|
||||
config JSA1212
|
||||
tristate "JSA1212 ALS and proximity sensor driver"
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here if you want to build a IIO driver for JSA1212
|
||||
proximity & ALS sensor device.
|
||||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called jsa1212.
|
||||
|
||||
config SENSORS_LM3533
|
||||
tristate "LM3533 ambient light sensor"
|
||||
depends on MFD_LM3533
|
||||
|
|
|
@ -7,11 +7,13 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o
|
|||
obj-$(CONFIG_AL3320A) += al3320a.o
|
||||
obj-$(CONFIG_APDS9300) += apds9300.o
|
||||
obj-$(CONFIG_CM32181) += cm32181.o
|
||||
obj-$(CONFIG_CM3232) += cm3232.o
|
||||
obj-$(CONFIG_CM36651) += cm36651.o
|
||||
obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o
|
||||
obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o
|
||||
obj-$(CONFIG_HID_SENSOR_PROX) += hid-sensor-prox.o
|
||||
obj-$(CONFIG_ISL29125) += isl29125.o
|
||||
obj-$(CONFIG_JSA1212) += jsa1212.o
|
||||
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
|
||||
obj-$(CONFIG_LTR501) += ltr501.o
|
||||
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
|
||||
|
|
|
@ -169,7 +169,7 @@ static int cm32181_write_als_it(struct cm32181_chip *cm32181, int val)
|
|||
* @cm32181: pointer of struct cm32181.
|
||||
*
|
||||
* Convert sensor raw data to lux. It depends on integration
|
||||
* time and claibscale variable.
|
||||
* time and calibscale variable.
|
||||
*
|
||||
* Return: Positive value is lux, otherwise is error code.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* CM3232 Ambient Light Sensor
|
||||
*
|
||||
* Copyright (C) 2014-2015 Capella Microsystems Inc.
|
||||
* Author: Kevin Tsai <ktsai@capellamicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2, as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* IIO driver for CM3232 (7-bit I2C slave address 0x10).
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
/* Registers Address */
|
||||
#define CM3232_REG_ADDR_CMD 0x00
|
||||
#define CM3232_REG_ADDR_ALS 0x50
|
||||
#define CM3232_REG_ADDR_ID 0x53
|
||||
|
||||
#define CM3232_CMD_ALS_DISABLE BIT(0)
|
||||
|
||||
#define CM3232_CMD_ALS_IT_SHIFT 2
|
||||
#define CM3232_CMD_ALS_IT_MASK (BIT(2) | BIT(3) | BIT(4))
|
||||
#define CM3232_CMD_ALS_IT_DEFAULT (0x01 << CM3232_CMD_ALS_IT_SHIFT)
|
||||
|
||||
#define CM3232_CMD_ALS_RESET BIT(6)
|
||||
|
||||
#define CM3232_CMD_DEFAULT CM3232_CMD_ALS_IT_DEFAULT
|
||||
|
||||
#define CM3232_HW_ID 0x32
|
||||
#define CM3232_CALIBSCALE_DEFAULT 100000
|
||||
#define CM3232_CALIBSCALE_RESOLUTION 100000
|
||||
#define CM3232_MLUX_PER_LUX 1000
|
||||
|
||||
#define CM3232_MLUX_PER_BIT_DEFAULT 64
|
||||
#define CM3232_MLUX_PER_BIT_BASE_IT 100000
|
||||
|
||||
static const struct {
|
||||
int val;
|
||||
int val2;
|
||||
u8 it;
|
||||
} cm3232_als_it_scales[] = {
|
||||
{0, 100000, 0}, /* 0.100000 */
|
||||
{0, 200000, 1}, /* 0.200000 */
|
||||
{0, 400000, 2}, /* 0.400000 */
|
||||
{0, 800000, 3}, /* 0.800000 */
|
||||
{1, 600000, 4}, /* 1.600000 */
|
||||
{3, 200000, 5}, /* 3.200000 */
|
||||
};
|
||||
|
||||
struct cm3232_als_info {
|
||||
u8 regs_cmd_default;
|
||||
u8 hw_id;
|
||||
int calibscale;
|
||||
int mlux_per_bit;
|
||||
int mlux_per_bit_base_it;
|
||||
};
|
||||
|
||||
static struct cm3232_als_info cm3232_als_info_default = {
|
||||
.regs_cmd_default = CM3232_CMD_DEFAULT,
|
||||
.hw_id = CM3232_HW_ID,
|
||||
.calibscale = CM3232_CALIBSCALE_DEFAULT,
|
||||
.mlux_per_bit = CM3232_MLUX_PER_BIT_DEFAULT,
|
||||
.mlux_per_bit_base_it = CM3232_MLUX_PER_BIT_BASE_IT,
|
||||
};
|
||||
|
||||
struct cm3232_chip {
|
||||
struct i2c_client *client;
|
||||
struct cm3232_als_info *als_info;
|
||||
u8 regs_cmd;
|
||||
u16 regs_als;
|
||||
};
|
||||
|
||||
/**
|
||||
* cm3232_reg_init() - Initialize CM3232
|
||||
* @chip: pointer of struct cm3232_chip.
|
||||
*
|
||||
* Check and initialize CM3232 ambient light sensor.
|
||||
*
|
||||
* Return: 0 for success; otherwise for error code.
|
||||
*/
|
||||
static int cm3232_reg_init(struct cm3232_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
s32 ret;
|
||||
|
||||
chip->als_info = &cm3232_als_info_default;
|
||||
|
||||
/* Identify device */
|
||||
ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ID);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "Error reading addr_id\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret & 0xFF) != chip->als_info->hw_id)
|
||||
return -ENODEV;
|
||||
|
||||
/* Disable and reset device */
|
||||
chip->regs_cmd = CM3232_CMD_ALS_DISABLE | CM3232_CMD_ALS_RESET;
|
||||
ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
|
||||
chip->regs_cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->client->dev, "Error writing reg_cmd\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register default value */
|
||||
chip->regs_cmd = chip->als_info->regs_cmd_default;
|
||||
|
||||
/* Configure register */
|
||||
ret = i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
|
||||
chip->regs_cmd);
|
||||
if (ret < 0)
|
||||
dev_err(&chip->client->dev, "Error writing reg_cmd\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm3232_read_als_it() - Get sensor integration time
|
||||
* @chip: pointer of struct cm3232_chip
|
||||
* @val: pointer of int to load the integration (sec).
|
||||
* @val2: pointer of int to load the integration time (microsecond).
|
||||
*
|
||||
* Report the current integration time.
|
||||
*
|
||||
* Return: IIO_VAL_INT_PLUS_MICRO for success, otherwise -EINVAL.
|
||||
*/
|
||||
static int cm3232_read_als_it(struct cm3232_chip *chip, int *val, int *val2)
|
||||
{
|
||||
u16 als_it;
|
||||
int i;
|
||||
|
||||
als_it = chip->regs_cmd;
|
||||
als_it &= CM3232_CMD_ALS_IT_MASK;
|
||||
als_it >>= CM3232_CMD_ALS_IT_SHIFT;
|
||||
for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
|
||||
if (als_it == cm3232_als_it_scales[i].it) {
|
||||
*val = cm3232_als_it_scales[i].val;
|
||||
*val2 = cm3232_als_it_scales[i].val2;
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm3232_write_als_it() - Write sensor integration time
|
||||
* @chip: pointer of struct cm3232_chip.
|
||||
* @val: integration time in second.
|
||||
* @val2: integration time in microsecond.
|
||||
*
|
||||
* Convert integration time to sensor value.
|
||||
*
|
||||
* Return: i2c_smbus_write_byte_data command return value.
|
||||
*/
|
||||
static int cm3232_write_als_it(struct cm3232_chip *chip, int val, int val2)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
u16 als_it, cmd;
|
||||
int i;
|
||||
s32 ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++) {
|
||||
if (val == cm3232_als_it_scales[i].val &&
|
||||
val2 == cm3232_als_it_scales[i].val2) {
|
||||
|
||||
als_it = cm3232_als_it_scales[i].it;
|
||||
als_it <<= CM3232_CMD_ALS_IT_SHIFT;
|
||||
|
||||
cmd = chip->regs_cmd & ~CM3232_CMD_ALS_IT_MASK;
|
||||
cmd |= als_it;
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
CM3232_REG_ADDR_CMD,
|
||||
cmd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
chip->regs_cmd = cmd;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm3232_get_lux() - report current lux value
|
||||
* @chip: pointer of struct cm3232_chip.
|
||||
*
|
||||
* Convert sensor data to lux. It depends on integration
|
||||
* time and calibscale variable.
|
||||
*
|
||||
* Return: Zero or positive value is lux, otherwise error code.
|
||||
*/
|
||||
static int cm3232_get_lux(struct cm3232_chip *chip)
|
||||
{
|
||||
struct i2c_client *client = chip->client;
|
||||
struct cm3232_als_info *als_info = chip->als_info;
|
||||
int ret;
|
||||
int val, val2;
|
||||
int als_it;
|
||||
u64 lux;
|
||||
|
||||
/* Calculate mlux per bit based on als_it */
|
||||
ret = cm3232_read_als_it(chip, &val, &val2);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
als_it = val * 1000000 + val2;
|
||||
lux = (__force u64)als_info->mlux_per_bit;
|
||||
lux *= als_info->mlux_per_bit_base_it;
|
||||
lux = div_u64(lux, als_it);
|
||||
|
||||
ret = i2c_smbus_read_word_data(client, CM3232_REG_ADDR_ALS);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error reading reg_addr_als\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
chip->regs_als = (u16)ret;
|
||||
lux *= chip->regs_als;
|
||||
lux *= als_info->calibscale;
|
||||
lux = div_u64(lux, CM3232_CALIBSCALE_RESOLUTION);
|
||||
lux = div_u64(lux, CM3232_MLUX_PER_LUX);
|
||||
|
||||
if (lux > 0xFFFF)
|
||||
lux = 0xFFFF;
|
||||
|
||||
return (int)lux;
|
||||
}
|
||||
|
||||
static int cm3232_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct cm3232_chip *chip = iio_priv(indio_dev);
|
||||
struct cm3232_als_info *als_info = chip->als_info;
|
||||
int ret;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_PROCESSED:
|
||||
ret = cm3232_get_lux(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
*val = als_info->calibscale;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
return cm3232_read_als_it(chip, val, val2);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int cm3232_write_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct cm3232_chip *chip = iio_priv(indio_dev);
|
||||
struct cm3232_als_info *als_info = chip->als_info;
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_CALIBSCALE:
|
||||
als_info->calibscale = val;
|
||||
return 0;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
return cm3232_write_als_it(chip, val, val2);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cm3232_get_it_available() - Get available ALS IT value
|
||||
* @dev: pointer of struct device.
|
||||
* @attr: pointer of struct device_attribute.
|
||||
* @buf: pointer of return string buffer.
|
||||
*
|
||||
* Display the available integration time in second.
|
||||
*
|
||||
* Return: string length.
|
||||
*/
|
||||
static ssize_t cm3232_get_it_available(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int i, len;
|
||||
|
||||
for (i = 0, len = 0; i < ARRAY_SIZE(cm3232_als_it_scales); i++)
|
||||
len += scnprintf(buf + len, PAGE_SIZE - len, "%u.%06u ",
|
||||
cm3232_als_it_scales[i].val,
|
||||
cm3232_als_it_scales[i].val2);
|
||||
return len + scnprintf(buf + len, PAGE_SIZE - len, "\n");
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec cm3232_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate =
|
||||
BIT(IIO_CHAN_INFO_PROCESSED) |
|
||||
BIT(IIO_CHAN_INFO_CALIBSCALE) |
|
||||
BIT(IIO_CHAN_INFO_INT_TIME),
|
||||
}
|
||||
};
|
||||
|
||||
static IIO_DEVICE_ATTR(in_illuminance_integration_time_available,
|
||||
S_IRUGO, cm3232_get_it_available, NULL, 0);
|
||||
|
||||
static struct attribute *cm3232_attributes[] = {
|
||||
&iio_dev_attr_in_illuminance_integration_time_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group cm3232_attribute_group = {
|
||||
.attrs = cm3232_attributes
|
||||
};
|
||||
|
||||
static const struct iio_info cm3232_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &cm3232_read_raw,
|
||||
.write_raw = &cm3232_write_raw,
|
||||
.attrs = &cm3232_attribute_group,
|
||||
};
|
||||
|
||||
static int cm3232_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct cm3232_chip *chip;
|
||||
struct iio_dev *indio_dev;
|
||||
int ret;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
chip = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
chip->client = client;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = cm3232_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(cm3232_channels);
|
||||
indio_dev->info = &cm3232_info;
|
||||
indio_dev->name = id->name;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
ret = cm3232_reg_init(chip);
|
||||
if (ret) {
|
||||
dev_err(&client->dev,
|
||||
"%s: register init failed\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return iio_device_register(indio_dev);
|
||||
}
|
||||
|
||||
static int cm3232_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
|
||||
i2c_smbus_write_byte_data(client, CM3232_REG_ADDR_CMD,
|
||||
CM3232_CMD_ALS_DISABLE);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id cm3232_id[] = {
|
||||
{"cm3232", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, cm3232_id);
|
||||
|
||||
static const struct of_device_id cm3232_of_match[] = {
|
||||
{.compatible = "capella,cm3232"},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct i2c_driver cm3232_driver = {
|
||||
.driver = {
|
||||
.name = "cm3232",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(cm3232_of_match),
|
||||
},
|
||||
.id_table = cm3232_id,
|
||||
.probe = cm3232_probe,
|
||||
.remove = cm3232_remove,
|
||||
};
|
||||
|
||||
module_i2c_driver(cm3232_driver);
|
||||
|
||||
MODULE_AUTHOR("Kevin Tsai <ktsai@capellamicro.com>");
|
||||
MODULE_DESCRIPTION("CM3232 ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -80,7 +80,6 @@ static int als_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
|
@ -97,15 +96,8 @@ static int als_read_raw(struct iio_dev *indio_dev,
|
|||
break;
|
||||
}
|
||||
if (report_id >= 0) {
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&als_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&als_state->common_attributes,
|
||||
true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
als_state->common_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_ALS, address,
|
||||
|
@ -381,6 +373,7 @@ static struct platform_driver hid_als_platform_driver = {
|
|||
.id_table = hid_als_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_als_probe,
|
||||
.remove = hid_als_remove,
|
||||
|
|
|
@ -75,7 +75,6 @@ static int prox_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
|
@ -92,16 +91,8 @@ static int prox_read_raw(struct iio_dev *indio_dev,
|
|||
break;
|
||||
}
|
||||
if (report_id >= 0) {
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&prox_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&prox_state->common_attributes,
|
||||
true);
|
||||
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
prox_state->common_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_PROX, address,
|
||||
|
@ -373,6 +364,7 @@ static struct platform_driver hid_prox_platform_driver = {
|
|||
.id_table = hid_prox_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_prox_probe,
|
||||
.remove = hid_prox_remove,
|
||||
|
|
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* JSA1212 Ambient Light & Proximity Sensor Driver
|
||||
*
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* JSA1212 I2C slave address: 0x44(ADDR tied to GND), 0x45(ADDR tied to VDD)
|
||||
*
|
||||
* TODO: Interrupt support, thresholds, range support.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
|
||||
/* JSA1212 reg address */
|
||||
#define JSA1212_CONF_REG 0x01
|
||||
#define JSA1212_INT_REG 0x02
|
||||
#define JSA1212_PXS_LT_REG 0x03
|
||||
#define JSA1212_PXS_HT_REG 0x04
|
||||
#define JSA1212_ALS_TH1_REG 0x05
|
||||
#define JSA1212_ALS_TH2_REG 0x06
|
||||
#define JSA1212_ALS_TH3_REG 0x07
|
||||
#define JSA1212_PXS_DATA_REG 0x08
|
||||
#define JSA1212_ALS_DT1_REG 0x09
|
||||
#define JSA1212_ALS_DT2_REG 0x0A
|
||||
#define JSA1212_ALS_RNG_REG 0x0B
|
||||
#define JSA1212_MAX_REG 0x0C
|
||||
|
||||
/* JSA1212 reg masks */
|
||||
#define JSA1212_CONF_MASK 0xFF
|
||||
#define JSA1212_INT_MASK 0xFF
|
||||
#define JSA1212_PXS_LT_MASK 0xFF
|
||||
#define JSA1212_PXS_HT_MASK 0xFF
|
||||
#define JSA1212_ALS_TH1_MASK 0xFF
|
||||
#define JSA1212_ALS_TH2_LT_MASK 0x0F
|
||||
#define JSA1212_ALS_TH2_HT_MASK 0xF0
|
||||
#define JSA1212_ALS_TH3_MASK 0xFF
|
||||
#define JSA1212_PXS_DATA_MASK 0xFF
|
||||
#define JSA1212_ALS_DATA_MASK 0x0FFF
|
||||
#define JSA1212_ALS_DT1_MASK 0xFF
|
||||
#define JSA1212_ALS_DT2_MASK 0x0F
|
||||
#define JSA1212_ALS_RNG_MASK 0x07
|
||||
|
||||
/* JSA1212 CONF REG bits */
|
||||
#define JSA1212_CONF_PXS_MASK 0x80
|
||||
#define JSA1212_CONF_PXS_ENABLE 0x80
|
||||
#define JSA1212_CONF_PXS_DISABLE 0x00
|
||||
#define JSA1212_CONF_ALS_MASK 0x04
|
||||
#define JSA1212_CONF_ALS_ENABLE 0x04
|
||||
#define JSA1212_CONF_ALS_DISABLE 0x00
|
||||
#define JSA1212_CONF_IRDR_MASK 0x08
|
||||
/* Proxmity sensing IRDR current sink settings */
|
||||
#define JSA1212_CONF_IRDR_200MA 0x08
|
||||
#define JSA1212_CONF_IRDR_100MA 0x00
|
||||
#define JSA1212_CONF_PXS_SLP_MASK 0x70
|
||||
#define JSA1212_CONF_PXS_SLP_0MS 0x70
|
||||
#define JSA1212_CONF_PXS_SLP_12MS 0x60
|
||||
#define JSA1212_CONF_PXS_SLP_50MS 0x50
|
||||
#define JSA1212_CONF_PXS_SLP_75MS 0x40
|
||||
#define JSA1212_CONF_PXS_SLP_100MS 0x30
|
||||
#define JSA1212_CONF_PXS_SLP_200MS 0x20
|
||||
#define JSA1212_CONF_PXS_SLP_400MS 0x10
|
||||
#define JSA1212_CONF_PXS_SLP_800MS 0x00
|
||||
|
||||
/* JSA1212 INT REG bits */
|
||||
#define JSA1212_INT_CTRL_MASK 0x01
|
||||
#define JSA1212_INT_CTRL_EITHER 0x00
|
||||
#define JSA1212_INT_CTRL_BOTH 0x01
|
||||
#define JSA1212_INT_ALS_PRST_MASK 0x06
|
||||
#define JSA1212_INT_ALS_PRST_1CONV 0x00
|
||||
#define JSA1212_INT_ALS_PRST_4CONV 0x02
|
||||
#define JSA1212_INT_ALS_PRST_8CONV 0x04
|
||||
#define JSA1212_INT_ALS_PRST_16CONV 0x06
|
||||
#define JSA1212_INT_ALS_FLAG_MASK 0x08
|
||||
#define JSA1212_INT_ALS_FLAG_CLR 0x00
|
||||
#define JSA1212_INT_PXS_PRST_MASK 0x60
|
||||
#define JSA1212_INT_PXS_PRST_1CONV 0x00
|
||||
#define JSA1212_INT_PXS_PRST_4CONV 0x20
|
||||
#define JSA1212_INT_PXS_PRST_8CONV 0x40
|
||||
#define JSA1212_INT_PXS_PRST_16CONV 0x60
|
||||
#define JSA1212_INT_PXS_FLAG_MASK 0x80
|
||||
#define JSA1212_INT_PXS_FLAG_CLR 0x00
|
||||
|
||||
/* JSA1212 ALS RNG REG bits */
|
||||
#define JSA1212_ALS_RNG_0_2048 0x00
|
||||
#define JSA1212_ALS_RNG_0_1024 0x01
|
||||
#define JSA1212_ALS_RNG_0_512 0x02
|
||||
#define JSA1212_ALS_RNG_0_256 0x03
|
||||
#define JSA1212_ALS_RNG_0_128 0x04
|
||||
|
||||
/* JSA1212 INT threshold range */
|
||||
#define JSA1212_ALS_TH_MIN 0x0000
|
||||
#define JSA1212_ALS_TH_MAX 0x0FFF
|
||||
#define JSA1212_PXS_TH_MIN 0x00
|
||||
#define JSA1212_PXS_TH_MAX 0xFF
|
||||
|
||||
#define JSA1212_ALS_DELAY_MS 200
|
||||
#define JSA1212_PXS_DELAY_MS 100
|
||||
|
||||
#define JSA1212_DRIVER_NAME "jsa1212"
|
||||
#define JSA1212_REGMAP_NAME "jsa1212_regmap"
|
||||
|
||||
enum jsa1212_op_mode {
|
||||
JSA1212_OPMODE_ALS_EN,
|
||||
JSA1212_OPMODE_PXS_EN,
|
||||
};
|
||||
|
||||
struct jsa1212_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u8 als_rng_idx;
|
||||
bool als_en; /* ALS enable status */
|
||||
bool pxs_en; /* proximity enable status */
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
/* ALS range idx to val mapping */
|
||||
static const int jsa1212_als_range_val[] = {2048, 1024, 512, 256, 128,
|
||||
128, 128, 128};
|
||||
|
||||
/* Enables or disables ALS function based on status */
|
||||
static int jsa1212_als_enable(struct jsa1212_data *data, u8 status)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
|
||||
JSA1212_CONF_ALS_MASK,
|
||||
status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->als_en = !!status;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enables or disables PXS function based on status */
|
||||
static int jsa1212_pxs_enable(struct jsa1212_data *data, u8 status)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
|
||||
JSA1212_CONF_PXS_MASK,
|
||||
status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->pxs_en = !!status;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jsa1212_read_als_data(struct jsa1212_data *data,
|
||||
unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
__le16 als_data;
|
||||
|
||||
ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Delay for data output */
|
||||
msleep(JSA1212_ALS_DELAY_MS);
|
||||
|
||||
/* Read 12 bit data */
|
||||
ret = regmap_bulk_read(data->regmap, JSA1212_ALS_DT1_REG, &als_data, 2);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "als data read err\n");
|
||||
goto als_data_read_err;
|
||||
}
|
||||
|
||||
*val = le16_to_cpu(als_data);
|
||||
|
||||
als_data_read_err:
|
||||
return jsa1212_als_enable(data, JSA1212_CONF_ALS_DISABLE);
|
||||
}
|
||||
|
||||
static int jsa1212_read_pxs_data(struct jsa1212_data *data,
|
||||
unsigned int *val)
|
||||
{
|
||||
int ret;
|
||||
unsigned int pxs_data;
|
||||
|
||||
ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Delay for data output */
|
||||
msleep(JSA1212_PXS_DELAY_MS);
|
||||
|
||||
/* Read out all data */
|
||||
ret = regmap_read(data->regmap, JSA1212_PXS_DATA_REG, &pxs_data);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "pxs data read err\n");
|
||||
goto pxs_data_read_err;
|
||||
}
|
||||
|
||||
*val = pxs_data & JSA1212_PXS_DATA_MASK;
|
||||
|
||||
pxs_data_read_err:
|
||||
return jsa1212_pxs_enable(data, JSA1212_CONF_PXS_DISABLE);
|
||||
}
|
||||
|
||||
static int jsa1212_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
int ret;
|
||||
struct jsa1212_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
mutex_lock(&data->lock);
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
ret = jsa1212_read_als_data(data, val);
|
||||
break;
|
||||
case IIO_PROXIMITY:
|
||||
ret = jsa1212_read_pxs_data(data, val);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&data->lock);
|
||||
return ret < 0 ? ret : IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
switch (chan->type) {
|
||||
case IIO_LIGHT:
|
||||
*val = jsa1212_als_range_val[data->als_rng_idx];
|
||||
*val2 = BIT(12); /* Max 12 bit value */
|
||||
return IIO_VAL_FRACTIONAL;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec jsa1212_channels[] = {
|
||||
{
|
||||
.type = IIO_LIGHT,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
||||
BIT(IIO_CHAN_INFO_SCALE),
|
||||
},
|
||||
{
|
||||
.type = IIO_PROXIMITY,
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
|
||||
}
|
||||
};
|
||||
|
||||
static const struct iio_info jsa1212_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.read_raw = &jsa1212_read_raw,
|
||||
};
|
||||
|
||||
static int jsa1212_chip_init(struct jsa1212_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(data->regmap, JSA1212_CONF_REG,
|
||||
(JSA1212_CONF_PXS_SLP_50MS |
|
||||
JSA1212_CONF_IRDR_200MA));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, JSA1212_INT_REG,
|
||||
JSA1212_INT_ALS_PRST_4CONV);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->als_rng_idx = JSA1212_ALS_RNG_0_2048;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool jsa1212_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case JSA1212_PXS_DATA_REG:
|
||||
case JSA1212_ALS_DT1_REG:
|
||||
case JSA1212_ALS_DT2_REG:
|
||||
case JSA1212_INT_REG:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct regmap_config jsa1212_regmap_config = {
|
||||
.name = JSA1212_REGMAP_NAME,
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = JSA1212_MAX_REG,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.volatile_reg = jsa1212_is_volatile_reg,
|
||||
};
|
||||
|
||||
static int jsa1212_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct jsa1212_data *data;
|
||||
struct iio_dev *indio_dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &jsa1212_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Regmap initialization failed.\n");
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data->client = client;
|
||||
data->regmap = regmap;
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
ret = jsa1212_chip_init(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = jsa1212_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(jsa1212_channels);
|
||||
indio_dev->name = JSA1212_DRIVER_NAME;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
|
||||
indio_dev->info = &jsa1212_info;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "%s: register device failed\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power off the device */
|
||||
static int jsa1212_power_off(struct jsa1212_data *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
ret = regmap_update_bits(data->regmap, JSA1212_CONF_REG,
|
||||
JSA1212_CONF_ALS_MASK |
|
||||
JSA1212_CONF_PXS_MASK,
|
||||
JSA1212_CONF_ALS_DISABLE |
|
||||
JSA1212_CONF_PXS_DISABLE);
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(&data->client->dev, "power off cmd failed\n");
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int jsa1212_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct jsa1212_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
|
||||
return jsa1212_power_off(data);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int jsa1212_suspend(struct device *dev)
|
||||
{
|
||||
struct jsa1212_data *data;
|
||||
|
||||
data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
|
||||
|
||||
return jsa1212_power_off(data);
|
||||
}
|
||||
|
||||
static int jsa1212_resume(struct device *dev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct jsa1212_data *data;
|
||||
|
||||
data = iio_priv(i2c_get_clientdata(to_i2c_client(dev)));
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
if (data->als_en) {
|
||||
ret = jsa1212_als_enable(data, JSA1212_CONF_ALS_ENABLE);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "als resume failed\n");
|
||||
goto unlock_and_ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->pxs_en) {
|
||||
ret = jsa1212_pxs_enable(data, JSA1212_CONF_PXS_ENABLE);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "pxs resume failed\n");
|
||||
}
|
||||
|
||||
unlock_and_ret:
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(jsa1212_pm_ops, jsa1212_suspend, jsa1212_resume);
|
||||
|
||||
#define JSA1212_PM_OPS (&jsa1212_pm_ops)
|
||||
#else
|
||||
#define JSA1212_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static const struct acpi_device_id jsa1212_acpi_match[] = {
|
||||
{"JSA1212", 0},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, jsa1212_acpi_match);
|
||||
|
||||
static const struct i2c_device_id jsa1212_id[] = {
|
||||
{ JSA1212_DRIVER_NAME, 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, jsa1212_id);
|
||||
|
||||
static struct i2c_driver jsa1212_driver = {
|
||||
.driver = {
|
||||
.name = JSA1212_DRIVER_NAME,
|
||||
.pm = JSA1212_PM_OPS,
|
||||
.owner = THIS_MODULE,
|
||||
.acpi_match_table = ACPI_PTR(jsa1212_acpi_match),
|
||||
},
|
||||
.probe = jsa1212_probe,
|
||||
.remove = jsa1212_remove,
|
||||
.id_table = jsa1212_id,
|
||||
};
|
||||
module_i2c_driver(jsa1212_driver);
|
||||
|
||||
MODULE_AUTHOR("Sathya Kuppuswamy <sathyanarayanan.kuppuswamy@linux.intel.com>");
|
||||
MODULE_DESCRIPTION("JSA1212 proximity/ambient light sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -657,7 +657,7 @@ static ALS_HYSTERESIS_ATTR_RO(3);
|
|||
#define ILLUMINANCE_ATTR_RO(_name) \
|
||||
DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
|
||||
#define ILLUMINANCE_ATTR_RW(_name) \
|
||||
DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
|
||||
DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR, \
|
||||
show_##_name, store_##_name)
|
||||
/*
|
||||
* ALS Zone threshold-event enable
|
||||
|
|
|
@ -149,8 +149,8 @@ static int tcs3414_read_raw(struct iio_dev *indio_dev,
|
|||
*val = ret;
|
||||
return IIO_VAL_INT;
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT;
|
||||
*val = tcs3414_scales[i][0];
|
||||
i = (data->gain & TCS3414_GAIN_MASK) >> TCS3414_GAIN_SHIFT;
|
||||
*val = tcs3414_scales[i][0];
|
||||
*val2 = tcs3414_scales[i][1];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
case IIO_CHAN_INFO_INT_TIME:
|
||||
|
|
|
@ -6,26 +6,21 @@
|
|||
menu "Magnetometer sensors"
|
||||
|
||||
config AK8975
|
||||
tristate "Asahi Kasei AK8975 3-Axis Magnetometer"
|
||||
tristate "Asahi Kasei AK 3-Axis Magnetometer"
|
||||
depends on I2C
|
||||
depends on GPIOLIB
|
||||
help
|
||||
Say yes here to build support for Asahi Kasei AK8975 3-Axis
|
||||
Magnetometer. This driver can also support AK8963, if i2c
|
||||
device name is identified as ak8963.
|
||||
Say yes here to build support for Asahi Kasei AK8975, AK8963,
|
||||
AK09911 or AK09912 3-Axis Magnetometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called ak8975.
|
||||
|
||||
config AK09911
|
||||
tristate "Asahi Kasei AK09911 3-axis Compass"
|
||||
depends on I2C
|
||||
select AK8975
|
||||
help
|
||||
Say yes here to build support for Asahi Kasei AK09911 3-Axis
|
||||
Magnetometer.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called ak09911.
|
||||
Deprecated: AK09911 is now supported by AK8975 driver.
|
||||
|
||||
config MAG3110
|
||||
tristate "Freescale MAG3110 3-Axis Magnetometer"
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#
|
||||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_AK09911) += ak09911.o
|
||||
obj-$(CONFIG_AK8975) += ak8975.o
|
||||
obj-$(CONFIG_MAG3110) += mag3110.o
|
||||
obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o
|
||||
|
|
|
@ -1,326 +0,0 @@
|
|||
/*
|
||||
* AK09911 3-axis compass driver
|
||||
* Copyright (c) 2014, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/iio/iio.h>
|
||||
|
||||
#define AK09911_REG_WIA1 0x00
|
||||
#define AK09911_REG_WIA2 0x01
|
||||
#define AK09911_WIA1_VALUE 0x48
|
||||
#define AK09911_WIA2_VALUE 0x05
|
||||
|
||||
#define AK09911_REG_ST1 0x10
|
||||
#define AK09911_REG_HXL 0x11
|
||||
#define AK09911_REG_HXH 0x12
|
||||
#define AK09911_REG_HYL 0x13
|
||||
#define AK09911_REG_HYH 0x14
|
||||
#define AK09911_REG_HZL 0x15
|
||||
#define AK09911_REG_HZH 0x16
|
||||
|
||||
#define AK09911_REG_ASAX 0x60
|
||||
#define AK09911_REG_ASAY 0x61
|
||||
#define AK09911_REG_ASAZ 0x62
|
||||
|
||||
#define AK09911_REG_CNTL1 0x30
|
||||
#define AK09911_REG_CNTL2 0x31
|
||||
#define AK09911_REG_CNTL3 0x32
|
||||
|
||||
#define AK09911_MODE_SNG_MEASURE 0x01
|
||||
#define AK09911_MODE_SELF_TEST 0x10
|
||||
#define AK09911_MODE_FUSE_ACCESS 0x1F
|
||||
#define AK09911_MODE_POWERDOWN 0x00
|
||||
#define AK09911_RESET_DATA 0x01
|
||||
|
||||
#define AK09911_REG_CNTL1 0x30
|
||||
#define AK09911_REG_CNTL2 0x31
|
||||
#define AK09911_REG_CNTL3 0x32
|
||||
|
||||
#define AK09911_RAW_TO_GAUSS(asa) ((((asa) + 128) * 6000) / 256)
|
||||
|
||||
#define AK09911_MAX_CONVERSION_TIMEOUT_MS 500
|
||||
#define AK09911_CONVERSION_DONE_POLL_TIME_MS 10
|
||||
|
||||
struct ak09911_data {
|
||||
struct i2c_client *client;
|
||||
struct mutex lock;
|
||||
u8 asa[3];
|
||||
long raw_to_gauss[3];
|
||||
};
|
||||
|
||||
static const int ak09911_index_to_reg[] = {
|
||||
AK09911_REG_HXL, AK09911_REG_HYL, AK09911_REG_HZL,
|
||||
};
|
||||
|
||||
static int ak09911_set_mode(struct i2c_client *client, u8 mode)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (mode) {
|
||||
case AK09911_MODE_SNG_MEASURE:
|
||||
case AK09911_MODE_SELF_TEST:
|
||||
case AK09911_MODE_FUSE_ACCESS:
|
||||
case AK09911_MODE_POWERDOWN:
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
AK09911_REG_CNTL2, mode);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "set_mode error\n");
|
||||
return ret;
|
||||
}
|
||||
/* After mode change wait atleast 100us */
|
||||
usleep_range(100, 500);
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev,
|
||||
"%s: Unknown mode(%d).", __func__, mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get Sensitivity Adjustment value */
|
||||
static int ak09911_get_asa(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct ak09911_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
ret = ak09911_set_mode(client, AK09911_MODE_FUSE_ACCESS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Get asa data and store in the device data. */
|
||||
ret = i2c_smbus_read_i2c_block_data(client, AK09911_REG_ASAX,
|
||||
3, data->asa);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Not able to read asa data\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = ak09911_set_mode(client, AK09911_MODE_POWERDOWN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->raw_to_gauss[0] = AK09911_RAW_TO_GAUSS(data->asa[0]);
|
||||
data->raw_to_gauss[1] = AK09911_RAW_TO_GAUSS(data->asa[1]);
|
||||
data->raw_to_gauss[2] = AK09911_RAW_TO_GAUSS(data->asa[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ak09911_verify_chip_id(struct i2c_client *client)
|
||||
{
|
||||
u8 wia_val[2];
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(client, AK09911_REG_WIA1,
|
||||
2, wia_val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error reading WIA\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev, "WIA %02x %02x\n", wia_val[0], wia_val[1]);
|
||||
|
||||
if (wia_val[0] != AK09911_WIA1_VALUE ||
|
||||
wia_val[1] != AK09911_WIA2_VALUE) {
|
||||
dev_err(&client->dev, "Device ak09911 not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wait_conversion_complete_polled(struct ak09911_data *data)
|
||||
{
|
||||
struct i2c_client *client = data->client;
|
||||
u8 read_status;
|
||||
u32 timeout_ms = AK09911_MAX_CONVERSION_TIMEOUT_MS;
|
||||
int ret;
|
||||
|
||||
/* Wait for the conversion to complete. */
|
||||
while (timeout_ms) {
|
||||
msleep_interruptible(AK09911_CONVERSION_DONE_POLL_TIME_MS);
|
||||
ret = i2c_smbus_read_byte_data(client, AK09911_REG_ST1);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in reading ST1\n");
|
||||
return ret;
|
||||
}
|
||||
read_status = ret & 0x01;
|
||||
if (read_status)
|
||||
break;
|
||||
timeout_ms -= AK09911_CONVERSION_DONE_POLL_TIME_MS;
|
||||
}
|
||||
if (!timeout_ms) {
|
||||
dev_err(&client->dev, "Conversion timeout happened\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return read_status;
|
||||
}
|
||||
|
||||
static int ak09911_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
||||
{
|
||||
struct ak09911_data *data = iio_priv(indio_dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
ret = ak09911_set_mode(client, AK09911_MODE_SNG_MEASURE);
|
||||
if (ret < 0)
|
||||
goto fn_exit;
|
||||
|
||||
ret = wait_conversion_complete_polled(data);
|
||||
if (ret < 0)
|
||||
goto fn_exit;
|
||||
|
||||
/* Read data */
|
||||
ret = i2c_smbus_read_word_data(client, ak09911_index_to_reg[index]);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Read axis data fails\n");
|
||||
goto fn_exit;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
/* Clamp to valid range. */
|
||||
*val = sign_extend32(clamp_t(s16, ret, -8192, 8191), 13);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
|
||||
fn_exit:
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ak09911_read_raw(struct iio_dev *indio_dev,
|
||||
struct iio_chan_spec const *chan,
|
||||
int *val, int *val2,
|
||||
long mask)
|
||||
{
|
||||
struct ak09911_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
return ak09911_read_axis(indio_dev, chan->address, val);
|
||||
case IIO_CHAN_INFO_SCALE:
|
||||
*val = 0;
|
||||
*val2 = data->raw_to_gauss[chan->address];
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#define AK09911_CHANNEL(axis, index) \
|
||||
{ \
|
||||
.type = IIO_MAGN, \
|
||||
.modified = 1, \
|
||||
.channel2 = IIO_MOD_##axis, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
||||
BIT(IIO_CHAN_INFO_SCALE), \
|
||||
.address = index, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec ak09911_channels[] = {
|
||||
AK09911_CHANNEL(X, 0), AK09911_CHANNEL(Y, 1), AK09911_CHANNEL(Z, 2),
|
||||
};
|
||||
|
||||
static const struct iio_info ak09911_info = {
|
||||
.read_raw = &ak09911_read_raw,
|
||||
.driver_module = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct acpi_device_id ak_acpi_match[] = {
|
||||
{"AK009911", 0},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, ak_acpi_match);
|
||||
|
||||
static int ak09911_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct iio_dev *indio_dev;
|
||||
struct ak09911_data *data;
|
||||
const char *name;
|
||||
int ret;
|
||||
|
||||
ret = ak09911_verify_chip_id(client);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "AK00911 not detected\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
ret = ak09911_get_asa(client);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (id)
|
||||
name = id->name;
|
||||
else if (ACPI_HANDLE(&client->dev))
|
||||
name = dev_name(&client->dev);
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
dev_dbg(&client->dev, "Asahi compass chip %s\n", name);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = ak09911_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ak09911_channels);
|
||||
indio_dev->info = &ak09911_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->name = name;
|
||||
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ak09911_id[] = {
|
||||
{"ak09911", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, ak09911_id);
|
||||
|
||||
static struct i2c_driver ak09911_driver = {
|
||||
.driver = {
|
||||
.name = "ak09911",
|
||||
.acpi_match_table = ACPI_PTR(ak_acpi_match),
|
||||
},
|
||||
.probe = ak09911_probe,
|
||||
.id_table = ak09911_id,
|
||||
};
|
||||
module_i2c_driver(ak09911_driver);
|
||||
|
||||
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("AK09911 Compass driver");
|
|
@ -64,10 +64,10 @@
|
|||
#define AK8975_REG_CNTL 0x0A
|
||||
#define AK8975_REG_CNTL_MODE_SHIFT 0
|
||||
#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT)
|
||||
#define AK8975_REG_CNTL_MODE_POWER_DOWN 0
|
||||
#define AK8975_REG_CNTL_MODE_ONCE 1
|
||||
#define AK8975_REG_CNTL_MODE_SELF_TEST 8
|
||||
#define AK8975_REG_CNTL_MODE_FUSE_ROM 0xF
|
||||
#define AK8975_REG_CNTL_MODE_POWER_DOWN 0x00
|
||||
#define AK8975_REG_CNTL_MODE_ONCE 0x01
|
||||
#define AK8975_REG_CNTL_MODE_SELF_TEST 0x08
|
||||
#define AK8975_REG_CNTL_MODE_FUSE_ROM 0x0F
|
||||
|
||||
#define AK8975_REG_RSVC 0x0B
|
||||
#define AK8975_REG_ASTC 0x0C
|
||||
|
@ -80,19 +80,279 @@
|
|||
|
||||
#define AK8975_MAX_REGS AK8975_REG_ASAZ
|
||||
|
||||
/*
|
||||
* AK09912 Register definitions
|
||||
*/
|
||||
#define AK09912_REG_WIA1 0x00
|
||||
#define AK09912_REG_WIA2 0x01
|
||||
#define AK09912_DEVICE_ID 0x04
|
||||
#define AK09911_DEVICE_ID 0x05
|
||||
|
||||
#define AK09911_REG_INFO1 0x02
|
||||
#define AK09911_REG_INFO2 0x03
|
||||
|
||||
#define AK09912_REG_ST1 0x10
|
||||
|
||||
#define AK09912_REG_ST1_DRDY_SHIFT 0
|
||||
#define AK09912_REG_ST1_DRDY_MASK (1 << AK09912_REG_ST1_DRDY_SHIFT)
|
||||
|
||||
#define AK09912_REG_HXL 0x11
|
||||
#define AK09912_REG_HXH 0x12
|
||||
#define AK09912_REG_HYL 0x13
|
||||
#define AK09912_REG_HYH 0x14
|
||||
#define AK09912_REG_HZL 0x15
|
||||
#define AK09912_REG_HZH 0x16
|
||||
#define AK09912_REG_TMPS 0x17
|
||||
|
||||
#define AK09912_REG_ST2 0x18
|
||||
#define AK09912_REG_ST2_HOFL_SHIFT 3
|
||||
#define AK09912_REG_ST2_HOFL_MASK (1 << AK09912_REG_ST2_HOFL_SHIFT)
|
||||
|
||||
#define AK09912_REG_CNTL1 0x30
|
||||
|
||||
#define AK09912_REG_CNTL2 0x31
|
||||
#define AK09912_REG_CNTL_MODE_POWER_DOWN 0x00
|
||||
#define AK09912_REG_CNTL_MODE_ONCE 0x01
|
||||
#define AK09912_REG_CNTL_MODE_SELF_TEST 0x10
|
||||
#define AK09912_REG_CNTL_MODE_FUSE_ROM 0x1F
|
||||
#define AK09912_REG_CNTL2_MODE_SHIFT 0
|
||||
#define AK09912_REG_CNTL2_MODE_MASK (0x1F << AK09912_REG_CNTL2_MODE_SHIFT)
|
||||
|
||||
#define AK09912_REG_CNTL3 0x32
|
||||
|
||||
#define AK09912_REG_TS1 0x33
|
||||
#define AK09912_REG_TS2 0x34
|
||||
#define AK09912_REG_TS3 0x35
|
||||
#define AK09912_REG_I2CDIS 0x36
|
||||
#define AK09912_REG_TS4 0x37
|
||||
|
||||
#define AK09912_REG_ASAX 0x60
|
||||
#define AK09912_REG_ASAY 0x61
|
||||
#define AK09912_REG_ASAZ 0x62
|
||||
|
||||
#define AK09912_MAX_REGS AK09912_REG_ASAZ
|
||||
|
||||
/*
|
||||
* Miscellaneous values.
|
||||
*/
|
||||
#define AK8975_MAX_CONVERSION_TIMEOUT 500
|
||||
#define AK8975_CONVERSION_DONE_POLL_TIME 10
|
||||
#define AK8975_DATA_READY_TIMEOUT ((100*HZ)/1000)
|
||||
#define RAW_TO_GAUSS_8975(asa) ((((asa) + 128) * 3000) / 256)
|
||||
#define RAW_TO_GAUSS_8963(asa) ((((asa) + 128) * 6000) / 256)
|
||||
|
||||
/*
|
||||
* Precalculate scale factor (in Gauss units) for each axis and
|
||||
* store in the device data.
|
||||
*
|
||||
* This scale factor is axis-dependent, and is derived from 3 calibration
|
||||
* factors ASA(x), ASA(y), and ASA(z).
|
||||
*
|
||||
* These ASA values are read from the sensor device at start of day, and
|
||||
* cached in the device context struct.
|
||||
*
|
||||
* Adjusting the flux value with the sensitivity adjustment value should be
|
||||
* done via the following formula:
|
||||
*
|
||||
* Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 )
|
||||
* where H is the raw value, ASA is the sensitivity adjustment, and Hadj
|
||||
* is the resultant adjusted value.
|
||||
*
|
||||
* We reduce the formula to:
|
||||
*
|
||||
* Hadj = H * (ASA + 128) / 256
|
||||
*
|
||||
* H is in the range of -4096 to 4095. The magnetometer has a range of
|
||||
* +-1229uT. To go from the raw value to uT is:
|
||||
*
|
||||
* HuT = H * 1229/4096, or roughly, 3/10.
|
||||
*
|
||||
* Since 1uT = 0.01 gauss, our final scale factor becomes:
|
||||
*
|
||||
* Hadj = H * ((ASA + 128) / 256) * 3/10 * 1/100
|
||||
* Hadj = H * ((ASA + 128) * 0.003) / 256
|
||||
*
|
||||
* Since ASA doesn't change, we cache the resultant scale factor into the
|
||||
* device context in ak8975_setup().
|
||||
*
|
||||
* Given we use IIO_VAL_INT_PLUS_MICRO bit when displaying the scale, we
|
||||
* multiply the stored scale value by 1e6.
|
||||
*/
|
||||
static long ak8975_raw_to_gauss(u16 data)
|
||||
{
|
||||
return (((long)data + 128) * 3000) / 256;
|
||||
}
|
||||
|
||||
/*
|
||||
* For AK8963 and AK09911, same calculation, but the device is less sensitive:
|
||||
*
|
||||
* H is in the range of +-8190. The magnetometer has a range of
|
||||
* +-4912uT. To go from the raw value to uT is:
|
||||
*
|
||||
* HuT = H * 4912/8190, or roughly, 6/10, instead of 3/10.
|
||||
*/
|
||||
|
||||
static long ak8963_09911_raw_to_gauss(u16 data)
|
||||
{
|
||||
return (((long)data + 128) * 6000) / 256;
|
||||
}
|
||||
|
||||
/*
|
||||
* For AK09912, same calculation, except the device is more sensitive:
|
||||
*
|
||||
* H is in the range of -32752 to 32752. The magnetometer has a range of
|
||||
* +-4912uT. To go from the raw value to uT is:
|
||||
*
|
||||
* HuT = H * 4912/32752, or roughly, 3/20, instead of 3/10.
|
||||
*/
|
||||
static long ak09912_raw_to_gauss(u16 data)
|
||||
{
|
||||
return (((long)data + 128) * 1500) / 256;
|
||||
}
|
||||
|
||||
/* Compatible Asahi Kasei Compass parts */
|
||||
enum asahi_compass_chipset {
|
||||
AK8975,
|
||||
AK8963,
|
||||
AK09911,
|
||||
AK09912,
|
||||
AK_MAX_TYPE
|
||||
};
|
||||
|
||||
enum ak_ctrl_reg_addr {
|
||||
ST1,
|
||||
ST2,
|
||||
CNTL,
|
||||
ASA_BASE,
|
||||
MAX_REGS,
|
||||
REGS_END,
|
||||
};
|
||||
|
||||
enum ak_ctrl_reg_mask {
|
||||
ST1_DRDY,
|
||||
ST2_HOFL,
|
||||
ST2_DERR,
|
||||
CNTL_MODE,
|
||||
MASK_END,
|
||||
};
|
||||
|
||||
enum ak_ctrl_mode {
|
||||
POWER_DOWN,
|
||||
MODE_ONCE,
|
||||
SELF_TEST,
|
||||
FUSE_ROM,
|
||||
MODE_END,
|
||||
};
|
||||
|
||||
struct ak_def {
|
||||
enum asahi_compass_chipset type;
|
||||
long (*raw_to_gauss)(u16 data);
|
||||
u16 range;
|
||||
u8 ctrl_regs[REGS_END];
|
||||
u8 ctrl_masks[MASK_END];
|
||||
u8 ctrl_modes[MODE_END];
|
||||
u8 data_regs[3];
|
||||
};
|
||||
|
||||
static struct ak_def ak_def_array[AK_MAX_TYPE] = {
|
||||
{
|
||||
.type = AK8975,
|
||||
.raw_to_gauss = ak8975_raw_to_gauss,
|
||||
.range = 4096,
|
||||
.ctrl_regs = {
|
||||
AK8975_REG_ST1,
|
||||
AK8975_REG_ST2,
|
||||
AK8975_REG_CNTL,
|
||||
AK8975_REG_ASAX,
|
||||
AK8975_MAX_REGS},
|
||||
.ctrl_masks = {
|
||||
AK8975_REG_ST1_DRDY_MASK,
|
||||
AK8975_REG_ST2_HOFL_MASK,
|
||||
AK8975_REG_ST2_DERR_MASK,
|
||||
AK8975_REG_CNTL_MODE_MASK},
|
||||
.ctrl_modes = {
|
||||
AK8975_REG_CNTL_MODE_POWER_DOWN,
|
||||
AK8975_REG_CNTL_MODE_ONCE,
|
||||
AK8975_REG_CNTL_MODE_SELF_TEST,
|
||||
AK8975_REG_CNTL_MODE_FUSE_ROM},
|
||||
.data_regs = {
|
||||
AK8975_REG_HXL,
|
||||
AK8975_REG_HYL,
|
||||
AK8975_REG_HZL},
|
||||
},
|
||||
{
|
||||
.type = AK8963,
|
||||
.raw_to_gauss = ak8963_09911_raw_to_gauss,
|
||||
.range = 8190,
|
||||
.ctrl_regs = {
|
||||
AK8975_REG_ST1,
|
||||
AK8975_REG_ST2,
|
||||
AK8975_REG_CNTL,
|
||||
AK8975_REG_ASAX,
|
||||
AK8975_MAX_REGS},
|
||||
.ctrl_masks = {
|
||||
AK8975_REG_ST1_DRDY_MASK,
|
||||
AK8975_REG_ST2_HOFL_MASK,
|
||||
0,
|
||||
AK8975_REG_CNTL_MODE_MASK},
|
||||
.ctrl_modes = {
|
||||
AK8975_REG_CNTL_MODE_POWER_DOWN,
|
||||
AK8975_REG_CNTL_MODE_ONCE,
|
||||
AK8975_REG_CNTL_MODE_SELF_TEST,
|
||||
AK8975_REG_CNTL_MODE_FUSE_ROM},
|
||||
.data_regs = {
|
||||
AK8975_REG_HXL,
|
||||
AK8975_REG_HYL,
|
||||
AK8975_REG_HZL},
|
||||
},
|
||||
{
|
||||
.type = AK09911,
|
||||
.raw_to_gauss = ak8963_09911_raw_to_gauss,
|
||||
.range = 8192,
|
||||
.ctrl_regs = {
|
||||
AK09912_REG_ST1,
|
||||
AK09912_REG_ST2,
|
||||
AK09912_REG_CNTL2,
|
||||
AK09912_REG_ASAX,
|
||||
AK09912_MAX_REGS},
|
||||
.ctrl_masks = {
|
||||
AK09912_REG_ST1_DRDY_MASK,
|
||||
AK09912_REG_ST2_HOFL_MASK,
|
||||
0,
|
||||
AK09912_REG_CNTL2_MODE_MASK},
|
||||
.ctrl_modes = {
|
||||
AK09912_REG_CNTL_MODE_POWER_DOWN,
|
||||
AK09912_REG_CNTL_MODE_ONCE,
|
||||
AK09912_REG_CNTL_MODE_SELF_TEST,
|
||||
AK09912_REG_CNTL_MODE_FUSE_ROM},
|
||||
.data_regs = {
|
||||
AK09912_REG_HXL,
|
||||
AK09912_REG_HYL,
|
||||
AK09912_REG_HZL},
|
||||
},
|
||||
{
|
||||
.type = AK09912,
|
||||
.raw_to_gauss = ak09912_raw_to_gauss,
|
||||
.range = 32752,
|
||||
.ctrl_regs = {
|
||||
AK09912_REG_ST1,
|
||||
AK09912_REG_ST2,
|
||||
AK09912_REG_CNTL2,
|
||||
AK09912_REG_ASAX,
|
||||
AK09912_MAX_REGS},
|
||||
.ctrl_masks = {
|
||||
AK09912_REG_ST1_DRDY_MASK,
|
||||
AK09912_REG_ST2_HOFL_MASK,
|
||||
0,
|
||||
AK09912_REG_CNTL2_MODE_MASK},
|
||||
.ctrl_modes = {
|
||||
AK09912_REG_CNTL_MODE_POWER_DOWN,
|
||||
AK09912_REG_CNTL_MODE_ONCE,
|
||||
AK09912_REG_CNTL_MODE_SELF_TEST,
|
||||
AK09912_REG_CNTL_MODE_FUSE_ROM},
|
||||
.data_regs = {
|
||||
AK09912_REG_HXL,
|
||||
AK09912_REG_HYL,
|
||||
AK09912_REG_HZL},
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -100,40 +360,82 @@ enum asahi_compass_chipset {
|
|||
*/
|
||||
struct ak8975_data {
|
||||
struct i2c_client *client;
|
||||
struct ak_def *def;
|
||||
struct attribute_group attrs;
|
||||
struct mutex lock;
|
||||
u8 asa[3];
|
||||
long raw_to_gauss[3];
|
||||
u8 reg_cache[AK8975_MAX_REGS];
|
||||
int eoc_gpio;
|
||||
int eoc_irq;
|
||||
wait_queue_head_t data_ready_queue;
|
||||
unsigned long flags;
|
||||
enum asahi_compass_chipset chipset;
|
||||
};
|
||||
|
||||
static const int ak8975_index_to_reg[] = {
|
||||
AK8975_REG_HXL, AK8975_REG_HYL, AK8975_REG_HZL,
|
||||
u8 cntl_cache;
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function to write to the I2C device's registers.
|
||||
* Return 0 if the i2c device is the one we expect.
|
||||
* return a negative error number otherwise
|
||||
*/
|
||||
static int ak8975_write_data(struct i2c_client *client,
|
||||
u8 reg, u8 val, u8 mask, u8 shift)
|
||||
static int ak8975_who_i_am(struct i2c_client *client,
|
||||
enum asahi_compass_chipset type)
|
||||
{
|
||||
u8 wia_val[2];
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Signature for each device:
|
||||
* Device | WIA1 | WIA2
|
||||
* AK09912 | DEVICE_ID | AK09912_DEVICE_ID
|
||||
* AK09911 | DEVICE_ID | AK09911_DEVICE_ID
|
||||
* AK8975 | DEVICE_ID | NA
|
||||
* AK8963 | DEVICE_ID | NA
|
||||
*/
|
||||
ret = i2c_smbus_read_i2c_block_data(client, AK09912_REG_WIA1,
|
||||
2, wia_val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error reading WIA\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (wia_val[0] != AK8975_DEVICE_ID)
|
||||
return -ENODEV;
|
||||
|
||||
switch (type) {
|
||||
case AK8975:
|
||||
case AK8963:
|
||||
return 0;
|
||||
case AK09911:
|
||||
if (wia_val[1] == AK09911_DEVICE_ID)
|
||||
return 0;
|
||||
break;
|
||||
case AK09912:
|
||||
if (wia_val[1] == AK09912_DEVICE_ID)
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "Type %d unknown\n", type);
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to write to CNTL register.
|
||||
*/
|
||||
static int ak8975_set_mode(struct ak8975_data *data, enum ak_ctrl_mode mode)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct ak8975_data *data = iio_priv(indio_dev);
|
||||
u8 regval;
|
||||
int ret;
|
||||
|
||||
regval = (data->reg_cache[reg] & ~mask) | (val << shift);
|
||||
ret = i2c_smbus_write_byte_data(client, reg, regval);
|
||||
regval = (data->cntl_cache & ~data->def->ctrl_masks[CNTL_MODE]) |
|
||||
data->def->ctrl_modes[mode];
|
||||
ret = i2c_smbus_write_byte_data(data->client,
|
||||
data->def->ctrl_regs[CNTL], regval);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Write to device fails status %x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
data->reg_cache[reg] = regval;
|
||||
data->cntl_cache = regval;
|
||||
/* After mode change wait atleast 100us */
|
||||
usleep_range(100, 500);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -166,8 +468,8 @@ static int ak8975_setup_irq(struct ak8975_data *data)
|
|||
irq = gpio_to_irq(data->eoc_gpio);
|
||||
|
||||
rc = devm_request_irq(&client->dev, irq, ak8975_irq_handler,
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
dev_name(&client->dev), data);
|
||||
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
||||
dev_name(&client->dev), data);
|
||||
if (rc < 0) {
|
||||
dev_err(&client->dev,
|
||||
"irq %d request failed, (gpio %d): %d\n",
|
||||
|
@ -191,34 +493,18 @@ static int ak8975_setup(struct i2c_client *client)
|
|||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct ak8975_data *data = iio_priv(indio_dev);
|
||||
u8 device_id;
|
||||
int ret;
|
||||
|
||||
/* Confirm that the device we're talking to is really an AK8975. */
|
||||
ret = i2c_smbus_read_byte_data(client, AK8975_REG_WIA);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error reading WIA\n");
|
||||
return ret;
|
||||
}
|
||||
device_id = ret;
|
||||
if (device_id != AK8975_DEVICE_ID) {
|
||||
dev_err(&client->dev, "Device ak8975 not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Write the fused rom access mode. */
|
||||
ret = ak8975_write_data(client,
|
||||
AK8975_REG_CNTL,
|
||||
AK8975_REG_CNTL_MODE_FUSE_ROM,
|
||||
AK8975_REG_CNTL_MODE_MASK,
|
||||
AK8975_REG_CNTL_MODE_SHIFT);
|
||||
ret = ak8975_set_mode(data, FUSE_ROM);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in setting fuse access mode\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get asa data and store in the device data. */
|
||||
ret = i2c_smbus_read_i2c_block_data(client, AK8975_REG_ASAX,
|
||||
ret = i2c_smbus_read_i2c_block_data(client,
|
||||
data->def->ctrl_regs[ASA_BASE],
|
||||
3, data->asa);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Not able to read asa data\n");
|
||||
|
@ -226,13 +512,13 @@ static int ak8975_setup(struct i2c_client *client)
|
|||
}
|
||||
|
||||
/* After reading fuse ROM data set power-down mode */
|
||||
ret = ak8975_write_data(client,
|
||||
AK8975_REG_CNTL,
|
||||
AK8975_REG_CNTL_MODE_POWER_DOWN,
|
||||
AK8975_REG_CNTL_MODE_MASK,
|
||||
AK8975_REG_CNTL_MODE_SHIFT);
|
||||
ret = ak8975_set_mode(data, POWER_DOWN);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in setting power-down mode\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (data->eoc_gpio > 0 || client->irq) {
|
||||
if (data->eoc_gpio > 0 || client->irq > 0) {
|
||||
ret = ak8975_setup_irq(data);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
|
@ -241,61 +527,9 @@ static int ak8975_setup(struct i2c_client *client)
|
|||
}
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in setting power-down mode\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Precalculate scale factor (in Gauss units) for each axis and
|
||||
* store in the device data.
|
||||
*
|
||||
* This scale factor is axis-dependent, and is derived from 3 calibration
|
||||
* factors ASA(x), ASA(y), and ASA(z).
|
||||
*
|
||||
* These ASA values are read from the sensor device at start of day, and
|
||||
* cached in the device context struct.
|
||||
*
|
||||
* Adjusting the flux value with the sensitivity adjustment value should be
|
||||
* done via the following formula:
|
||||
*
|
||||
* Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 )
|
||||
*
|
||||
* where H is the raw value, ASA is the sensitivity adjustment, and Hadj
|
||||
* is the resultant adjusted value.
|
||||
*
|
||||
* We reduce the formula to:
|
||||
*
|
||||
* Hadj = H * (ASA + 128) / 256
|
||||
*
|
||||
* H is in the range of -4096 to 4095. The magnetometer has a range of
|
||||
* +-1229uT. To go from the raw value to uT is:
|
||||
*
|
||||
* HuT = H * 1229/4096, or roughly, 3/10.
|
||||
*
|
||||
* Since 1uT = 0.01 gauss, our final scale factor becomes:
|
||||
*
|
||||
* Hadj = H * ((ASA + 128) / 256) * 3/10 * 1/100
|
||||
* Hadj = H * ((ASA + 128) * 0.003) / 256
|
||||
*
|
||||
* Since ASA doesn't change, we cache the resultant scale factor into the
|
||||
* device context in ak8975_setup().
|
||||
*/
|
||||
if (data->chipset == AK8963) {
|
||||
/*
|
||||
* H range is +-8190 and magnetometer range is +-4912.
|
||||
* So HuT using the above explanation for 8975,
|
||||
* 4912/8190 = ~ 6/10.
|
||||
* So the Hadj should use 6/10 instead of 3/10.
|
||||
*/
|
||||
data->raw_to_gauss[0] = RAW_TO_GAUSS_8963(data->asa[0]);
|
||||
data->raw_to_gauss[1] = RAW_TO_GAUSS_8963(data->asa[1]);
|
||||
data->raw_to_gauss[2] = RAW_TO_GAUSS_8963(data->asa[2]);
|
||||
} else {
|
||||
data->raw_to_gauss[0] = RAW_TO_GAUSS_8975(data->asa[0]);
|
||||
data->raw_to_gauss[1] = RAW_TO_GAUSS_8975(data->asa[1]);
|
||||
data->raw_to_gauss[2] = RAW_TO_GAUSS_8975(data->asa[2]);
|
||||
}
|
||||
data->raw_to_gauss[0] = data->def->raw_to_gauss(data->asa[0]);
|
||||
data->raw_to_gauss[1] = data->def->raw_to_gauss(data->asa[1]);
|
||||
data->raw_to_gauss[2] = data->def->raw_to_gauss(data->asa[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -318,7 +552,7 @@ static int wait_conversion_complete_gpio(struct ak8975_data *data)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, AK8975_REG_ST1);
|
||||
ret = i2c_smbus_read_byte_data(client, data->def->ctrl_regs[ST1]);
|
||||
if (ret < 0)
|
||||
dev_err(&client->dev, "Error in reading ST1\n");
|
||||
|
||||
|
@ -335,7 +569,8 @@ static int wait_conversion_complete_polled(struct ak8975_data *data)
|
|||
/* Wait for the conversion to complete. */
|
||||
while (timeout_ms) {
|
||||
msleep(AK8975_CONVERSION_DONE_POLL_TIME);
|
||||
ret = i2c_smbus_read_byte_data(client, AK8975_REG_ST1);
|
||||
ret = i2c_smbus_read_byte_data(client,
|
||||
data->def->ctrl_regs[ST1]);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in reading ST1\n");
|
||||
return ret;
|
||||
|
@ -378,11 +613,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
mutex_lock(&data->lock);
|
||||
|
||||
/* Set up the device for taking a sample. */
|
||||
ret = ak8975_write_data(client,
|
||||
AK8975_REG_CNTL,
|
||||
AK8975_REG_CNTL_MODE_ONCE,
|
||||
AK8975_REG_CNTL_MODE_MASK,
|
||||
AK8975_REG_CNTL_MODE_SHIFT);
|
||||
ret = ak8975_set_mode(data, MODE_ONCE);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in setting operating mode\n");
|
||||
goto exit;
|
||||
|
@ -399,14 +630,15 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
goto exit;
|
||||
|
||||
/* This will be executed only for non-interrupt based waiting case */
|
||||
if (ret & AK8975_REG_ST1_DRDY_MASK) {
|
||||
ret = i2c_smbus_read_byte_data(client, AK8975_REG_ST2);
|
||||
if (ret & data->def->ctrl_masks[ST1_DRDY]) {
|
||||
ret = i2c_smbus_read_byte_data(client,
|
||||
data->def->ctrl_regs[ST2]);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Error in reading ST2\n");
|
||||
goto exit;
|
||||
}
|
||||
if (ret & (AK8975_REG_ST2_DERR_MASK |
|
||||
AK8975_REG_ST2_HOFL_MASK)) {
|
||||
if (ret & (data->def->ctrl_masks[ST2_DERR] |
|
||||
data->def->ctrl_masks[ST2_HOFL])) {
|
||||
dev_err(&client->dev, "ST2 status error 0x%x\n", ret);
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
|
@ -415,7 +647,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
|
||||
/* Read the flux value from the appropriate register
|
||||
(the register is specified in the iio device attributes). */
|
||||
ret = i2c_smbus_read_word_data(client, ak8975_index_to_reg[index]);
|
||||
ret = i2c_smbus_read_word_data(client, data->def->data_regs[index]);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Read axis data fails\n");
|
||||
goto exit;
|
||||
|
@ -424,7 +656,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val)
|
|||
mutex_unlock(&data->lock);
|
||||
|
||||
/* Clamp to valid range. */
|
||||
*val = clamp_t(s16, ret, -4096, 4095);
|
||||
*val = clamp_t(s16, ret, -data->def->range, data->def->range);
|
||||
return IIO_VAL_INT;
|
||||
|
||||
exit:
|
||||
|
@ -473,6 +705,8 @@ static const struct acpi_device_id ak_acpi_match[] = {
|
|||
{"AK8975", AK8975},
|
||||
{"AK8963", AK8963},
|
||||
{"INVN6500", AK8963},
|
||||
{"AK09911", AK09911},
|
||||
{"AK09912", AK09912},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, ak_acpi_match);
|
||||
|
@ -498,6 +732,7 @@ static int ak8975_probe(struct i2c_client *client,
|
|||
int eoc_gpio;
|
||||
int err;
|
||||
const char *name = NULL;
|
||||
enum asahi_compass_chipset chipset;
|
||||
|
||||
/* Grab and set up the supplied GPIO. */
|
||||
if (client->dev.platform_data)
|
||||
|
@ -537,42 +772,50 @@ static int ak8975_probe(struct i2c_client *client,
|
|||
|
||||
/* id will be NULL when enumerated via ACPI */
|
||||
if (id) {
|
||||
data->chipset =
|
||||
(enum asahi_compass_chipset)(id->driver_data);
|
||||
chipset = (enum asahi_compass_chipset)(id->driver_data);
|
||||
name = id->name;
|
||||
} else if (ACPI_HANDLE(&client->dev))
|
||||
name = ak8975_match_acpi_device(&client->dev, &data->chipset);
|
||||
name = ak8975_match_acpi_device(&client->dev, &chipset);
|
||||
else
|
||||
return -ENOSYS;
|
||||
|
||||
if (chipset >= AK_MAX_TYPE) {
|
||||
dev_err(&client->dev, "AKM device type unsupported: %d\n",
|
||||
chipset);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->def = &ak_def_array[chipset];
|
||||
err = ak8975_who_i_am(client, data->def->type);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev, "Unexpected device\n");
|
||||
return err;
|
||||
}
|
||||
dev_dbg(&client->dev, "Asahi compass chip %s\n", name);
|
||||
|
||||
/* Perform some basic start-of-day setup of the device. */
|
||||
err = ak8975_setup(client);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev, "AK8975 initialization fails\n");
|
||||
dev_err(&client->dev, "%s initialization fails\n", name);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->client = client;
|
||||
mutex_init(&data->lock);
|
||||
data->eoc_gpio = eoc_gpio;
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->channels = ak8975_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(ak8975_channels);
|
||||
indio_dev->info = &ak8975_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
indio_dev->name = name;
|
||||
err = devm_iio_device_register(&client->dev, indio_dev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
return devm_iio_device_register(&client->dev, indio_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ak8975_id[] = {
|
||||
{"ak8975", AK8975},
|
||||
{"ak8963", AK8963},
|
||||
{"AK8963", AK8963},
|
||||
{"ak09911", AK09911},
|
||||
{"ak09912", AK09912},
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -581,14 +824,20 @@ MODULE_DEVICE_TABLE(i2c, ak8975_id);
|
|||
static const struct of_device_id ak8975_of_match[] = {
|
||||
{ .compatible = "asahi-kasei,ak8975", },
|
||||
{ .compatible = "ak8975", },
|
||||
{ }
|
||||
{ .compatible = "asahi-kasei,ak8963", },
|
||||
{ .compatible = "ak8963", },
|
||||
{ .compatible = "asahi-kasei,ak09911", },
|
||||
{ .compatible = "ak09911", },
|
||||
{ .compatible = "asahi-kasei,ak09912", },
|
||||
{ .compatible = "ak09912", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ak8975_of_match);
|
||||
|
||||
static struct i2c_driver ak8975_driver = {
|
||||
.driver = {
|
||||
.name = "ak8975",
|
||||
.of_match_table = ak8975_of_match,
|
||||
.of_match_table = of_match_ptr(ak8975_of_match),
|
||||
.acpi_match_table = ACPI_PTR(ak_acpi_match),
|
||||
},
|
||||
.probe = ak8975_probe,
|
||||
|
|
|
@ -157,20 +157,12 @@ static int magn_3d_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case 0:
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&magn_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&magn_state->common_attributes, true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
report_id =
|
||||
magn_state->magn[chan->address].report_id;
|
||||
address = magn_3d_addresses[chan->address];
|
||||
|
@ -530,6 +522,7 @@ static struct platform_driver hid_magn_3d_platform_driver = {
|
|||
.id_table = hid_magn_3d_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_magn_3d_probe,
|
||||
.remove = hid_magn_3d_remove,
|
||||
|
|
|
@ -111,20 +111,12 @@ static int incl_3d_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&incl_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
hid_sensor_power_state(&incl_state->common_attributes, true);
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
report_id =
|
||||
incl_state->incl[chan->scan_index].report_id;
|
||||
address = incl_3d_addresses[chan->scan_index];
|
||||
|
@ -437,6 +429,7 @@ static struct platform_driver hid_incl_3d_platform_driver = {
|
|||
.id_table = hid_incl_3d_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_incl_3d_probe,
|
||||
.remove = hid_incl_3d_remove,
|
||||
|
|
|
@ -80,16 +80,12 @@ struct bmp280_data {
|
|||
s32 t_fine;
|
||||
};
|
||||
|
||||
/* Compensation parameters. */
|
||||
struct bmp280_comp_temp {
|
||||
u16 dig_t1;
|
||||
s16 dig_t2, dig_t3;
|
||||
};
|
||||
|
||||
struct bmp280_comp_press {
|
||||
u16 dig_p1;
|
||||
s16 dig_p2, dig_p3, dig_p4, dig_p5, dig_p6, dig_p7, dig_p8, dig_p9;
|
||||
};
|
||||
/*
|
||||
* These enums are used for indexing into the array of compensation
|
||||
* parameters.
|
||||
*/
|
||||
enum { T1, T2, T3 };
|
||||
enum { P1, P2, P3, P4, P5, P6, P7, P8, P9 };
|
||||
|
||||
static const struct iio_chan_spec bmp280_channels[] = {
|
||||
{
|
||||
|
@ -141,10 +137,18 @@ static const struct regmap_config bmp280_regmap_config = {
|
|||
.volatile_reg = bmp280_is_volatile_reg,
|
||||
};
|
||||
|
||||
static int bmp280_read_compensation_temp(struct bmp280_data *data,
|
||||
struct bmp280_comp_temp *comp)
|
||||
/*
|
||||
* Returns temperature in DegC, resolution is 0.01 DegC. Output value of
|
||||
* "5123" equals 51.23 DegC. t_fine carries fine temperature as global
|
||||
* value.
|
||||
*
|
||||
* Taken from datasheet, Section 3.11.3, "Compensation formula".
|
||||
*/
|
||||
static s32 bmp280_compensate_temp(struct bmp280_data *data,
|
||||
s32 adc_temp)
|
||||
{
|
||||
int ret;
|
||||
s32 var1, var2;
|
||||
__le16 buf[BMP280_COMP_TEMP_REG_COUNT / 2];
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_TEMP_START,
|
||||
|
@ -155,63 +159,21 @@ static int bmp280_read_compensation_temp(struct bmp280_data *data,
|
|||
return ret;
|
||||
}
|
||||
|
||||
comp->dig_t1 = (u16) le16_to_cpu(buf[0]);
|
||||
comp->dig_t2 = (s16) le16_to_cpu(buf[1]);
|
||||
comp->dig_t3 = (s16) le16_to_cpu(buf[2]);
|
||||
/*
|
||||
* The double casts are necessary because le16_to_cpu returns an
|
||||
* unsigned 16-bit value. Casting that value directly to a
|
||||
* signed 32-bit will not do proper sign extension.
|
||||
*
|
||||
* Conversely, T1 and P1 are unsigned values, so they can be
|
||||
* cast straight to the larger type.
|
||||
*/
|
||||
var1 = (((adc_temp >> 3) - ((s32)le16_to_cpu(buf[T1]) << 1)) *
|
||||
((s32)(s16)le16_to_cpu(buf[T2]))) >> 11;
|
||||
var2 = (((((adc_temp >> 4) - ((s32)le16_to_cpu(buf[T1]))) *
|
||||
((adc_temp >> 4) - ((s32)le16_to_cpu(buf[T1])))) >> 12) *
|
||||
((s32)(s16)le16_to_cpu(buf[T3]))) >> 14;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bmp280_read_compensation_press(struct bmp280_data *data,
|
||||
struct bmp280_comp_press *comp)
|
||||
{
|
||||
int ret;
|
||||
__le16 buf[BMP280_COMP_PRESS_REG_COUNT / 2];
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_PRESS_START,
|
||||
buf, BMP280_COMP_PRESS_REG_COUNT);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"failed to read pressure calibration parameters\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
comp->dig_p1 = (u16) le16_to_cpu(buf[0]);
|
||||
comp->dig_p2 = (s16) le16_to_cpu(buf[1]);
|
||||
comp->dig_p3 = (s16) le16_to_cpu(buf[2]);
|
||||
comp->dig_p4 = (s16) le16_to_cpu(buf[3]);
|
||||
comp->dig_p5 = (s16) le16_to_cpu(buf[4]);
|
||||
comp->dig_p6 = (s16) le16_to_cpu(buf[5]);
|
||||
comp->dig_p7 = (s16) le16_to_cpu(buf[6]);
|
||||
comp->dig_p8 = (s16) le16_to_cpu(buf[7]);
|
||||
comp->dig_p9 = (s16) le16_to_cpu(buf[8]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns temperature in DegC, resolution is 0.01 DegC. Output value of
|
||||
* "5123" equals 51.23 DegC. t_fine carries fine temperature as global
|
||||
* value.
|
||||
*
|
||||
* Taken from datasheet, Section 3.11.3, "Compensation formula".
|
||||
*/
|
||||
static s32 bmp280_compensate_temp(struct bmp280_data *data,
|
||||
struct bmp280_comp_temp *comp,
|
||||
s32 adc_temp)
|
||||
{
|
||||
s32 var1, var2, t;
|
||||
|
||||
var1 = (((adc_temp >> 3) - ((s32) comp->dig_t1 << 1)) *
|
||||
((s32) comp->dig_t2)) >> 11;
|
||||
var2 = (((((adc_temp >> 4) - ((s32) comp->dig_t1)) *
|
||||
((adc_temp >> 4) - ((s32) comp->dig_t1))) >> 12) *
|
||||
((s32) comp->dig_t3)) >> 14;
|
||||
|
||||
data->t_fine = var1 + var2;
|
||||
t = (data->t_fine * 5 + 128) >> 8;
|
||||
|
||||
return t;
|
||||
return (data->t_fine * 5 + 128) >> 8;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -222,29 +184,38 @@ static s32 bmp280_compensate_temp(struct bmp280_data *data,
|
|||
* Taken from datasheet, Section 3.11.3, "Compensation formula".
|
||||
*/
|
||||
static u32 bmp280_compensate_press(struct bmp280_data *data,
|
||||
struct bmp280_comp_press *comp,
|
||||
s32 adc_press)
|
||||
{
|
||||
int ret;
|
||||
s64 var1, var2, p;
|
||||
__le16 buf[BMP280_COMP_PRESS_REG_COUNT / 2];
|
||||
|
||||
var1 = ((s64) data->t_fine) - 128000;
|
||||
var2 = var1 * var1 * (s64) comp->dig_p6;
|
||||
var2 = var2 + ((var1 * (s64) comp->dig_p5) << 17);
|
||||
var2 = var2 + (((s64) comp->dig_p4) << 35);
|
||||
var1 = ((var1 * var1 * (s64) comp->dig_p3) >> 8) +
|
||||
((var1 * (s64) comp->dig_p2) << 12);
|
||||
var1 = (((((s64) 1) << 47) + var1)) * ((s64) comp->dig_p1) >> 33;
|
||||
ret = regmap_bulk_read(data->regmap, BMP280_REG_COMP_PRESS_START,
|
||||
buf, BMP280_COMP_PRESS_REG_COUNT);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"failed to read pressure calibration parameters\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
var1 = ((s64)data->t_fine) - 128000;
|
||||
var2 = var1 * var1 * (s64)(s16)le16_to_cpu(buf[P6]);
|
||||
var2 += (var1 * (s64)(s16)le16_to_cpu(buf[P5])) << 17;
|
||||
var2 += ((s64)(s16)le16_to_cpu(buf[P4])) << 35;
|
||||
var1 = ((var1 * var1 * (s64)(s16)le16_to_cpu(buf[P3])) >> 8) +
|
||||
((var1 * (s64)(s16)le16_to_cpu(buf[P2])) << 12);
|
||||
var1 = ((((s64)1) << 47) + var1) * ((s64)le16_to_cpu(buf[P1])) >> 33;
|
||||
|
||||
if (var1 == 0)
|
||||
return 0;
|
||||
|
||||
p = ((((s64) 1048576 - adc_press) << 31) - var2) * 3125;
|
||||
p = ((((s64)1048576 - adc_press) << 31) - var2) * 3125;
|
||||
p = div64_s64(p, var1);
|
||||
var1 = (((s64) comp->dig_p9) * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (((s64) comp->dig_p8) * p) >> 19;
|
||||
p = ((p + var1 + var2) >> 8) + (((s64) comp->dig_p7) << 4);
|
||||
var1 = (((s64)(s16)le16_to_cpu(buf[P9])) * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (((s64)(s16)le16_to_cpu(buf[P8])) * p) >> 19;
|
||||
p = ((p + var1 + var2) >> 8) + (((s64)(s16)le16_to_cpu(buf[P7])) << 4);
|
||||
|
||||
return (u32) p;
|
||||
return (u32)p;
|
||||
}
|
||||
|
||||
static int bmp280_read_temp(struct bmp280_data *data,
|
||||
|
@ -253,11 +224,6 @@ static int bmp280_read_temp(struct bmp280_data *data,
|
|||
int ret;
|
||||
__be32 tmp = 0;
|
||||
s32 adc_temp, comp_temp;
|
||||
struct bmp280_comp_temp comp;
|
||||
|
||||
ret = bmp280_read_compensation_temp(data, &comp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMP280_REG_TEMP_MSB,
|
||||
(u8 *) &tmp, 3);
|
||||
|
@ -267,7 +233,7 @@ static int bmp280_read_temp(struct bmp280_data *data,
|
|||
}
|
||||
|
||||
adc_temp = be32_to_cpu(tmp) >> 12;
|
||||
comp_temp = bmp280_compensate_temp(data, &comp, adc_temp);
|
||||
comp_temp = bmp280_compensate_temp(data, adc_temp);
|
||||
|
||||
/*
|
||||
* val might be NULL if we're called by the read_press routine,
|
||||
|
@ -288,11 +254,6 @@ static int bmp280_read_press(struct bmp280_data *data,
|
|||
__be32 tmp = 0;
|
||||
s32 adc_press;
|
||||
u32 comp_press;
|
||||
struct bmp280_comp_press comp;
|
||||
|
||||
ret = bmp280_read_compensation_press(data, &comp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Read and compensate temperature so we get a reading of t_fine. */
|
||||
ret = bmp280_read_temp(data, NULL);
|
||||
|
@ -307,7 +268,7 @@ static int bmp280_read_press(struct bmp280_data *data,
|
|||
}
|
||||
|
||||
adc_press = be32_to_cpu(tmp) >> 12;
|
||||
comp_press = bmp280_compensate_press(data, &comp, adc_press);
|
||||
comp_press = bmp280_compensate_press(data, adc_press);
|
||||
|
||||
*val = comp_press;
|
||||
*val2 = 256000;
|
||||
|
@ -366,7 +327,7 @@ static int bmp280_chip_init(struct bmp280_data *data)
|
|||
BMP280_MODE_NORMAL);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev,
|
||||
"failed to write config register\n");
|
||||
"failed to write ctrl_meas register\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -394,7 +355,6 @@ static int bmp280_probe(struct i2c_client *client,
|
|||
if (!indio_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
data = iio_priv(indio_dev);
|
||||
mutex_init(&data->lock);
|
||||
data->client = client;
|
||||
|
|
|
@ -79,7 +79,6 @@ static int press_read_raw(struct iio_dev *indio_dev,
|
|||
int report_id = -1;
|
||||
u32 address;
|
||||
int ret_type;
|
||||
s32 poll_value;
|
||||
|
||||
*val = 0;
|
||||
*val2 = 0;
|
||||
|
@ -96,15 +95,8 @@ static int press_read_raw(struct iio_dev *indio_dev,
|
|||
break;
|
||||
}
|
||||
if (report_id >= 0) {
|
||||
poll_value = hid_sensor_read_poll_value(
|
||||
&press_state->common_attributes);
|
||||
if (poll_value < 0)
|
||||
return -EINVAL;
|
||||
hid_sensor_power_state(&press_state->common_attributes,
|
||||
true);
|
||||
|
||||
msleep_interruptible(poll_value * 2);
|
||||
|
||||
*val = sensor_hub_input_attr_get_raw_value(
|
||||
press_state->common_attributes.hsdev,
|
||||
HID_USAGE_SENSOR_PRESSURE, address,
|
||||
|
@ -382,6 +374,7 @@ static struct platform_driver hid_press_platform_driver = {
|
|||
.id_table = hid_press_ids,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.pm = &hid_sensor_pm_ops,
|
||||
},
|
||||
.probe = hid_press_probe,
|
||||
.remove = hid_press_remove,
|
||||
|
|
|
@ -17,3 +17,20 @@ config AS3935
|
|||
module will be called as3935
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Proximity sensors"
|
||||
|
||||
config SX9500
|
||||
tristate "SX9500 Semtech proximity sensor"
|
||||
select IIO_BUFFER
|
||||
select IIO_TRIGGERED_BUFFER
|
||||
select REGMAP_I2C
|
||||
depends on I2C
|
||||
help
|
||||
Say Y here to build a driver for Semtech's SX9500 capacitive
|
||||
proximity/button sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called sx9500.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
# When adding new entries keep the list in alphabetical order
|
||||
obj-$(CONFIG_AS3935) += as3935.o
|
||||
obj-$(CONFIG_SX9500) += sx9500.o
|
||||
|
|
|
@ -273,9 +273,9 @@ static void calibrate_as3935(struct as3935_state *st)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int as3935_suspend(struct spi_device *spi, pm_message_t msg)
|
||||
static int as3935_suspend(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct as3935_state *st = iio_priv(indio_dev);
|
||||
int val, ret;
|
||||
|
||||
|
@ -293,9 +293,9 @@ static int as3935_suspend(struct spi_device *spi, pm_message_t msg)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int as3935_resume(struct spi_device *spi)
|
||||
static int as3935_resume(struct device *dev)
|
||||
{
|
||||
struct iio_dev *indio_dev = spi_get_drvdata(spi);
|
||||
struct iio_dev *indio_dev = dev_get_drvdata(dev);
|
||||
struct as3935_state *st = iio_priv(indio_dev);
|
||||
int val, ret;
|
||||
|
||||
|
@ -311,9 +311,12 @@ static int as3935_resume(struct spi_device *spi)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(as3935_pm_ops, as3935_suspend, as3935_resume);
|
||||
#define AS3935_PM_OPS (&as3935_pm_ops)
|
||||
|
||||
#else
|
||||
#define as3935_suspend NULL
|
||||
#define as3935_resume NULL
|
||||
#define AS3935_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static int as3935_probe(struct spi_device *spi)
|
||||
|
@ -441,12 +444,11 @@ static struct spi_driver as3935_driver = {
|
|||
.driver = {
|
||||
.name = "as3935",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = AS3935_PM_OPS,
|
||||
},
|
||||
.probe = as3935_probe,
|
||||
.remove = as3935_remove,
|
||||
.id_table = as3935_id,
|
||||
.suspend = as3935_suspend,
|
||||
.resume = as3935_resume,
|
||||
};
|
||||
module_spi_driver(as3935_driver);
|
||||
|
||||
|
|
|
@ -0,0 +1,752 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Intel Corporation
|
||||
*
|
||||
* Driver for Semtech's SX9500 capacitive proximity/button solution.
|
||||
* Datasheet available at
|
||||
* <http://www.semtech.com/images/datasheet/sx9500.pdf>.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/iio/buffer.h>
|
||||
#include <linux/iio/sysfs.h>
|
||||
#include <linux/iio/events.h>
|
||||
#include <linux/iio/trigger.h>
|
||||
#include <linux/iio/triggered_buffer.h>
|
||||
#include <linux/iio/trigger_consumer.h>
|
||||
|
||||
#define SX9500_DRIVER_NAME "sx9500"
|
||||
#define SX9500_IRQ_NAME "sx9500_event"
|
||||
#define SX9500_GPIO_NAME "sx9500_gpio"
|
||||
|
||||
/* Register definitions. */
|
||||
#define SX9500_REG_IRQ_SRC 0x00
|
||||
#define SX9500_REG_STAT 0x01
|
||||
#define SX9500_REG_IRQ_MSK 0x03
|
||||
|
||||
#define SX9500_REG_PROX_CTRL0 0x06
|
||||
#define SX9500_REG_PROX_CTRL1 0x07
|
||||
#define SX9500_REG_PROX_CTRL2 0x08
|
||||
#define SX9500_REG_PROX_CTRL3 0x09
|
||||
#define SX9500_REG_PROX_CTRL4 0x0a
|
||||
#define SX9500_REG_PROX_CTRL5 0x0b
|
||||
#define SX9500_REG_PROX_CTRL6 0x0c
|
||||
#define SX9500_REG_PROX_CTRL7 0x0d
|
||||
#define SX9500_REG_PROX_CTRL8 0x0e
|
||||
|
||||
#define SX9500_REG_SENSOR_SEL 0x20
|
||||
#define SX9500_REG_USE_MSB 0x21
|
||||
#define SX9500_REG_USE_LSB 0x22
|
||||
#define SX9500_REG_AVG_MSB 0x23
|
||||
#define SX9500_REG_AVG_LSB 0x24
|
||||
#define SX9500_REG_DIFF_MSB 0x25
|
||||
#define SX9500_REG_DIFF_LSB 0x26
|
||||
#define SX9500_REG_OFFSET_MSB 0x27
|
||||
#define SX9500_REG_OFFSET_LSB 0x28
|
||||
|
||||
#define SX9500_REG_RESET 0x7f
|
||||
|
||||
/* Write this to REG_RESET to do a soft reset. */
|
||||
#define SX9500_SOFT_RESET 0xde
|
||||
|
||||
#define SX9500_SCAN_PERIOD_MASK GENMASK(6, 4)
|
||||
#define SX9500_SCAN_PERIOD_SHIFT 4
|
||||
|
||||
/*
|
||||
* These serve for identifying IRQ source in the IRQ_SRC register, and
|
||||
* also for masking the IRQs in the IRQ_MSK register.
|
||||
*/
|
||||
#define SX9500_CLOSE_IRQ BIT(6)
|
||||
#define SX9500_FAR_IRQ BIT(5)
|
||||
#define SX9500_CONVDONE_IRQ BIT(3)
|
||||
|
||||
#define SX9500_PROXSTAT_SHIFT 4
|
||||
|
||||
#define SX9500_NUM_CHANNELS 4
|
||||
|
||||
struct sx9500_data {
|
||||
struct mutex mutex;
|
||||
struct i2c_client *client;
|
||||
struct iio_trigger *trig;
|
||||
struct regmap *regmap;
|
||||
/*
|
||||
* Last reading of the proximity status for each channel. We
|
||||
* only send an event to user space when this changes.
|
||||
*/
|
||||
bool prox_stat[SX9500_NUM_CHANNELS];
|
||||
bool event_enabled[SX9500_NUM_CHANNELS];
|
||||
bool trigger_enabled;
|
||||
u16 *buffer;
|
||||
};
|
||||
|
||||
static const struct iio_event_spec sx9500_events[] = {
|
||||
{
|
||||
.type = IIO_EV_TYPE_THRESH,
|
||||
.dir = IIO_EV_DIR_EITHER,
|
||||
.mask_separate = BIT(IIO_EV_INFO_ENABLE),
|
||||
},
|
||||
};
|
||||
|
||||
#define SX9500_CHANNEL(idx) \
|
||||
{ \
|
||||
.type = IIO_PROXIMITY, \
|
||||
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
||||
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
|
||||
.indexed = 1, \
|
||||
.channel = idx, \
|
||||
.event_spec = sx9500_events, \
|
||||
.num_event_specs = ARRAY_SIZE(sx9500_events), \
|
||||
.scan_index = idx, \
|
||||
.scan_type = { \
|
||||
.sign = 'u', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.shift = 0, \
|
||||
}, \
|
||||
}
|
||||
|
||||
static const struct iio_chan_spec sx9500_channels[] = {
|
||||
SX9500_CHANNEL(0),
|
||||
SX9500_CHANNEL(1),
|
||||
SX9500_CHANNEL(2),
|
||||
SX9500_CHANNEL(3),
|
||||
IIO_CHAN_SOFT_TIMESTAMP(4),
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int val;
|
||||
int val2;
|
||||
} sx9500_samp_freq_table[] = {
|
||||
{33, 333333},
|
||||
{16, 666666},
|
||||
{11, 111111},
|
||||
{8, 333333},
|
||||
{6, 666666},
|
||||
{5, 0},
|
||||
{3, 333333},
|
||||
{2, 500000},
|
||||
};
|
||||
|
||||
static const struct regmap_range sx9500_writable_reg_ranges[] = {
|
||||
regmap_reg_range(SX9500_REG_IRQ_MSK, SX9500_REG_IRQ_MSK),
|
||||
regmap_reg_range(SX9500_REG_PROX_CTRL0, SX9500_REG_PROX_CTRL8),
|
||||
regmap_reg_range(SX9500_REG_SENSOR_SEL, SX9500_REG_SENSOR_SEL),
|
||||
regmap_reg_range(SX9500_REG_OFFSET_MSB, SX9500_REG_OFFSET_LSB),
|
||||
regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table sx9500_writeable_regs = {
|
||||
.yes_ranges = sx9500_writable_reg_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(sx9500_writable_reg_ranges),
|
||||
};
|
||||
|
||||
/*
|
||||
* All allocated registers are readable, so we just list unallocated
|
||||
* ones.
|
||||
*/
|
||||
static const struct regmap_range sx9500_non_readable_reg_ranges[] = {
|
||||
regmap_reg_range(SX9500_REG_STAT + 1, SX9500_REG_STAT + 1),
|
||||
regmap_reg_range(SX9500_REG_IRQ_MSK + 1, SX9500_REG_PROX_CTRL0 - 1),
|
||||
regmap_reg_range(SX9500_REG_PROX_CTRL8 + 1, SX9500_REG_SENSOR_SEL - 1),
|
||||
regmap_reg_range(SX9500_REG_OFFSET_LSB + 1, SX9500_REG_RESET - 1),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table sx9500_readable_regs = {
|
||||
.no_ranges = sx9500_non_readable_reg_ranges,
|
||||
.n_no_ranges = ARRAY_SIZE(sx9500_non_readable_reg_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_range sx9500_volatile_reg_ranges[] = {
|
||||
regmap_reg_range(SX9500_REG_IRQ_SRC, SX9500_REG_STAT),
|
||||
regmap_reg_range(SX9500_REG_USE_MSB, SX9500_REG_OFFSET_LSB),
|
||||
regmap_reg_range(SX9500_REG_RESET, SX9500_REG_RESET),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table sx9500_volatile_regs = {
|
||||
.yes_ranges = sx9500_volatile_reg_ranges,
|
||||
.n_yes_ranges = ARRAY_SIZE(sx9500_volatile_reg_ranges),
|
||||
};
|
||||
|
||||
static const struct regmap_config sx9500_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = SX9500_REG_RESET,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
|
||||
.wr_table = &sx9500_writeable_regs,
|
||||
.rd_table = &sx9500_readable_regs,
|
||||
.volatile_table = &sx9500_volatile_regs,
|
||||
};
|
||||
|
||||
static int sx9500_read_proximity(struct sx9500_data *data,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val)
|
||||
{
|
||||
int ret;
|
||||
__be16 regval;
|
||||
|
||||
ret = regmap_write(data->regmap, SX9500_REG_SENSOR_SEL, chan->channel);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, SX9500_REG_USE_MSB, ®val, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*val = 32767 - (s16)be16_to_cpu(regval);
|
||||
|
||||
return IIO_VAL_INT;
|
||||
}
|
||||
|
||||
static int sx9500_read_samp_freq(struct sx9500_data *data,
|
||||
int *val, int *val2)
|
||||
{
|
||||
int ret;
|
||||
unsigned int regval;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = regmap_read(data->regmap, SX9500_REG_PROX_CTRL0, ®val);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
regval = (regval & SX9500_SCAN_PERIOD_MASK) >> SX9500_SCAN_PERIOD_SHIFT;
|
||||
*val = sx9500_samp_freq_table[regval].val;
|
||||
*val2 = sx9500_samp_freq_table[regval].val2;
|
||||
|
||||
return IIO_VAL_INT_PLUS_MICRO;
|
||||
}
|
||||
|
||||
static int sx9500_read_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int *val, int *val2, long mask)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_PROXIMITY:
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_RAW:
|
||||
if (iio_buffer_enabled(indio_dev))
|
||||
return -EBUSY;
|
||||
mutex_lock(&data->mutex);
|
||||
ret = sx9500_read_proximity(data, chan, val);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return sx9500_read_samp_freq(data, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int sx9500_set_samp_freq(struct sx9500_data *data,
|
||||
int val, int val2)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sx9500_samp_freq_table); i++)
|
||||
if (val == sx9500_samp_freq_table[i].val &&
|
||||
val2 == sx9500_samp_freq_table[i].val2)
|
||||
break;
|
||||
|
||||
if (i == ARRAY_SIZE(sx9500_samp_freq_table))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
ret = regmap_update_bits(data->regmap, SX9500_REG_PROX_CTRL0,
|
||||
SX9500_SCAN_PERIOD_MASK,
|
||||
i << SX9500_SCAN_PERIOD_SHIFT);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sx9500_write_raw(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
int val, int val2, long mask)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
|
||||
switch (chan->type) {
|
||||
case IIO_PROXIMITY:
|
||||
switch (mask) {
|
||||
case IIO_CHAN_INFO_SAMP_FREQ:
|
||||
return sx9500_set_samp_freq(data, val, val2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t sx9500_irq_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (data->trigger_enabled)
|
||||
iio_trigger_poll(data->trig);
|
||||
|
||||
/*
|
||||
* Even if no event is enabled, we need to wake the thread to
|
||||
* clear the interrupt state by reading SX9500_REG_IRQ_SRC. It
|
||||
* is not possible to do that here because regmap_read takes a
|
||||
* mutex.
|
||||
*/
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static irqreturn_t sx9500_irq_thread_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_dev *indio_dev = private;
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
unsigned int val, chan;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "i2c transfer error in irq\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(val & (SX9500_CLOSE_IRQ | SX9500_FAR_IRQ)))
|
||||
goto out;
|
||||
|
||||
ret = regmap_read(data->regmap, SX9500_REG_STAT, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->client->dev, "i2c transfer error in irq\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
val >>= SX9500_PROXSTAT_SHIFT;
|
||||
for (chan = 0; chan < SX9500_NUM_CHANNELS; chan++) {
|
||||
int dir;
|
||||
u64 ev;
|
||||
bool new_prox = val & BIT(chan);
|
||||
|
||||
if (!data->event_enabled[chan])
|
||||
continue;
|
||||
if (new_prox == data->prox_stat[chan])
|
||||
/* No change on this channel. */
|
||||
continue;
|
||||
|
||||
dir = new_prox ? IIO_EV_DIR_FALLING :
|
||||
IIO_EV_DIR_RISING;
|
||||
ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY,
|
||||
chan,
|
||||
IIO_EV_TYPE_THRESH,
|
||||
dir);
|
||||
iio_push_event(indio_dev, ev, iio_get_time_ns());
|
||||
data->prox_stat[chan] = new_prox;
|
||||
}
|
||||
|
||||
out:
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int sx9500_read_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
|
||||
if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH ||
|
||||
dir != IIO_EV_DIR_EITHER)
|
||||
return -EINVAL;
|
||||
|
||||
return data->event_enabled[chan->channel];
|
||||
}
|
||||
|
||||
static int sx9500_write_event_config(struct iio_dev *indio_dev,
|
||||
const struct iio_chan_spec *chan,
|
||||
enum iio_event_type type,
|
||||
enum iio_event_direction dir,
|
||||
int state)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int ret, i;
|
||||
bool any_active = false;
|
||||
unsigned int irqmask;
|
||||
|
||||
if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH ||
|
||||
dir != IIO_EV_DIR_EITHER)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
data->event_enabled[chan->channel] = state;
|
||||
|
||||
for (i = 0; i < SX9500_NUM_CHANNELS; i++)
|
||||
if (data->event_enabled[i]) {
|
||||
any_active = true;
|
||||
break;
|
||||
}
|
||||
|
||||
irqmask = SX9500_CLOSE_IRQ | SX9500_FAR_IRQ;
|
||||
if (any_active)
|
||||
ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK,
|
||||
irqmask, irqmask);
|
||||
else
|
||||
ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK,
|
||||
irqmask, 0);
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sx9500_update_scan_mode(struct iio_dev *indio_dev,
|
||||
const unsigned long *scan_mask)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
kfree(data->buffer);
|
||||
data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
if (data->buffer == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(
|
||||
"2.500000 3.333333 5 6.666666 8.333333 11.111111 16.666666 33.333333");
|
||||
|
||||
static struct attribute *sx9500_attributes[] = {
|
||||
&iio_const_attr_sampling_frequency_available.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group sx9500_attribute_group = {
|
||||
.attrs = sx9500_attributes,
|
||||
};
|
||||
|
||||
static const struct iio_info sx9500_info = {
|
||||
.driver_module = THIS_MODULE,
|
||||
.attrs = &sx9500_attribute_group,
|
||||
.read_raw = &sx9500_read_raw,
|
||||
.write_raw = &sx9500_write_raw,
|
||||
.read_event_config = &sx9500_read_event_config,
|
||||
.write_event_config = &sx9500_write_event_config,
|
||||
.update_scan_mode = &sx9500_update_scan_mode,
|
||||
};
|
||||
|
||||
static int sx9500_set_trigger_state(struct iio_trigger *trig,
|
||||
bool state)
|
||||
{
|
||||
struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
ret = regmap_update_bits(data->regmap, SX9500_REG_IRQ_MSK,
|
||||
SX9500_CONVDONE_IRQ,
|
||||
state ? SX9500_CONVDONE_IRQ : 0);
|
||||
if (ret == 0)
|
||||
data->trigger_enabled = state;
|
||||
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct iio_trigger_ops sx9500_trigger_ops = {
|
||||
.set_trigger_state = sx9500_set_trigger_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static irqreturn_t sx9500_trigger_handler(int irq, void *private)
|
||||
{
|
||||
struct iio_poll_func *pf = private;
|
||||
struct iio_dev *indio_dev = pf->indio_dev;
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int val, bit, ret, i = 0;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
|
||||
for_each_set_bit(bit, indio_dev->buffer->scan_mask,
|
||||
indio_dev->masklength) {
|
||||
ret = sx9500_read_proximity(data, &indio_dev->channels[bit],
|
||||
&val);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
data->buffer[i++] = val;
|
||||
}
|
||||
|
||||
iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
|
||||
iio_get_time_ns());
|
||||
|
||||
out:
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
iio_trigger_notify_done(indio_dev->trig);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
struct sx9500_reg_default {
|
||||
u8 reg;
|
||||
u8 def;
|
||||
};
|
||||
|
||||
static const struct sx9500_reg_default sx9500_default_regs[] = {
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL1,
|
||||
/* Shield enabled, small range. */
|
||||
.def = 0x43,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL2,
|
||||
/* x8 gain, 167kHz frequency, finest resolution. */
|
||||
.def = 0x77,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL3,
|
||||
/* Doze enabled, 2x scan period doze, no raw filter. */
|
||||
.def = 0x40,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL4,
|
||||
/* Average threshold. */
|
||||
.def = 0x30,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL5,
|
||||
/*
|
||||
* Debouncer off, lowest average negative filter,
|
||||
* highest average postive filter.
|
||||
*/
|
||||
.def = 0x0f,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL6,
|
||||
/* Proximity detection threshold: 280 */
|
||||
.def = 0x0e,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL7,
|
||||
/*
|
||||
* No automatic compensation, compensate each pin
|
||||
* independently, proximity hysteresis: 32, close
|
||||
* debouncer off, far debouncer off.
|
||||
*/
|
||||
.def = 0x00,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL8,
|
||||
/* No stuck timeout, no periodic compensation. */
|
||||
.def = 0x00,
|
||||
},
|
||||
{
|
||||
.reg = SX9500_REG_PROX_CTRL0,
|
||||
/* Scan period: 30ms, all sensors enabled. */
|
||||
.def = 0x0f,
|
||||
},
|
||||
};
|
||||
|
||||
static int sx9500_init_device(struct iio_dev *indio_dev)
|
||||
{
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
int ret, i;
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_write(data->regmap, SX9500_REG_IRQ_MSK, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, SX9500_REG_RESET,
|
||||
SX9500_SOFT_RESET);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(data->regmap, SX9500_REG_IRQ_SRC, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sx9500_default_regs); i++) {
|
||||
ret = regmap_write(data->regmap,
|
||||
sx9500_default_regs[i].reg,
|
||||
sx9500_default_regs[i].def);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sx9500_gpio_probe(struct i2c_client *client,
|
||||
struct sx9500_data *data)
|
||||
{
|
||||
struct device *dev;
|
||||
struct gpio_desc *gpio;
|
||||
int ret;
|
||||
|
||||
if (!client)
|
||||
return -EINVAL;
|
||||
|
||||
dev = &client->dev;
|
||||
|
||||
/* data ready gpio interrupt pin */
|
||||
gpio = devm_gpiod_get_index(dev, SX9500_GPIO_NAME, 0);
|
||||
if (IS_ERR(gpio)) {
|
||||
dev_err(dev, "acpi gpio get index failed\n");
|
||||
return PTR_ERR(gpio);
|
||||
}
|
||||
|
||||
ret = gpiod_direction_input(gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = gpiod_to_irq(gpio);
|
||||
|
||||
dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sx9500_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct iio_dev *indio_dev;
|
||||
struct sx9500_data *data;
|
||||
|
||||
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
|
||||
if (indio_dev == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
data = iio_priv(indio_dev);
|
||||
data->client = client;
|
||||
mutex_init(&data->mutex);
|
||||
data->trigger_enabled = false;
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &sx9500_regmap_config);
|
||||
if (IS_ERR(data->regmap))
|
||||
return PTR_ERR(data->regmap);
|
||||
|
||||
sx9500_init_device(indio_dev);
|
||||
|
||||
indio_dev->dev.parent = &client->dev;
|
||||
indio_dev->name = SX9500_DRIVER_NAME;
|
||||
indio_dev->channels = sx9500_channels;
|
||||
indio_dev->num_channels = ARRAY_SIZE(sx9500_channels);
|
||||
indio_dev->info = &sx9500_info;
|
||||
indio_dev->modes = INDIO_DIRECT_MODE;
|
||||
i2c_set_clientdata(client, indio_dev);
|
||||
|
||||
if (client->irq <= 0)
|
||||
client->irq = sx9500_gpio_probe(client, data);
|
||||
|
||||
if (client->irq > 0) {
|
||||
ret = devm_request_threaded_irq(&client->dev, client->irq,
|
||||
sx9500_irq_handler, sx9500_irq_thread_handler,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
SX9500_IRQ_NAME, indio_dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
data->trig = devm_iio_trigger_alloc(&client->dev,
|
||||
"%s-dev%d", indio_dev->name, indio_dev->id);
|
||||
if (!data->trig)
|
||||
return -ENOMEM;
|
||||
|
||||
data->trig->dev.parent = &client->dev;
|
||||
data->trig->ops = &sx9500_trigger_ops;
|
||||
iio_trigger_set_drvdata(data->trig, indio_dev);
|
||||
|
||||
ret = iio_trigger_register(data->trig);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = iio_triggered_buffer_setup(indio_dev, NULL,
|
||||
sx9500_trigger_handler, NULL);
|
||||
if (ret < 0)
|
||||
goto out_trigger_unregister;
|
||||
|
||||
ret = iio_device_register(indio_dev);
|
||||
if (ret < 0)
|
||||
goto out_buffer_cleanup;
|
||||
|
||||
return 0;
|
||||
|
||||
out_buffer_cleanup:
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
out_trigger_unregister:
|
||||
if (client->irq > 0)
|
||||
iio_trigger_unregister(data->trig);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sx9500_remove(struct i2c_client *client)
|
||||
{
|
||||
struct iio_dev *indio_dev = i2c_get_clientdata(client);
|
||||
struct sx9500_data *data = iio_priv(indio_dev);
|
||||
|
||||
iio_device_unregister(indio_dev);
|
||||
iio_triggered_buffer_cleanup(indio_dev);
|
||||
if (client->irq > 0)
|
||||
iio_trigger_unregister(data->trig);
|
||||
kfree(data->buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id sx9500_acpi_match[] = {
|
||||
{"SSX9500", 0},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, sx9500_acpi_match);
|
||||
|
||||
static const struct i2c_device_id sx9500_id[] = {
|
||||
{"sx9500", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, sx9500_id);
|
||||
|
||||
static struct i2c_driver sx9500_driver = {
|
||||
.driver = {
|
||||
.name = SX9500_DRIVER_NAME,
|
||||
.acpi_match_table = ACPI_PTR(sx9500_acpi_match),
|
||||
},
|
||||
.probe = sx9500_probe,
|
||||
.remove = sx9500_remove,
|
||||
.id_table = sx9500_id,
|
||||
};
|
||||
module_i2c_driver(sx9500_driver);
|
||||
|
||||
MODULE_AUTHOR("Vlad Dogaru <vlad.dogaru@intel.com>");
|
||||
MODULE_DESCRIPTION("Driver for Semtech SX9500 proximity sensor");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -135,6 +135,7 @@ static int iio_sysfs_trigger_probe(int id)
|
|||
struct iio_sysfs_trig *t;
|
||||
int ret;
|
||||
bool foundit = false;
|
||||
|
||||
mutex_lock(&iio_sysfs_trig_list_mut);
|
||||
list_for_each_entry(t, &iio_sysfs_trig_list, l)
|
||||
if (id == t->id) {
|
||||
|
@ -185,6 +186,7 @@ static int iio_sysfs_trigger_remove(int id)
|
|||
{
|
||||
bool foundit = false;
|
||||
struct iio_sysfs_trig *t;
|
||||
|
||||
mutex_lock(&iio_sysfs_trig_list_mut);
|
||||
list_for_each_entry(t, &iio_sysfs_trig_list, l)
|
||||
if (id == t->id) {
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
# Makefile for MPT based block devices
|
||||
#
|
||||
|
||||
obj-$(CONFIG_I2O) += i2o/
|
||||
obj-$(CONFIG_FUSION) += fusion/
|
||||
|
|
|
@ -56,6 +56,8 @@ source "drivers/staging/vt6656/Kconfig"
|
|||
|
||||
source "drivers/staging/iio/Kconfig"
|
||||
|
||||
source "drivers/staging/sm7xxfb/Kconfig"
|
||||
|
||||
source "drivers/staging/xgifb/Kconfig"
|
||||
|
||||
source "drivers/staging/emxx_udc/Kconfig"
|
||||
|
@ -64,8 +66,6 @@ source "drivers/staging/ft1000/Kconfig"
|
|||
|
||||
source "drivers/staging/speakup/Kconfig"
|
||||
|
||||
source "drivers/staging/cptm1217/Kconfig"
|
||||
|
||||
source "drivers/staging/ste_rmi4/Kconfig"
|
||||
|
||||
source "drivers/staging/nvec/Kconfig"
|
||||
|
@ -104,4 +104,8 @@ source "drivers/staging/unisys/Kconfig"
|
|||
|
||||
source "drivers/staging/clocking-wizard/Kconfig"
|
||||
|
||||
source "drivers/staging/fbtft/Kconfig"
|
||||
|
||||
source "drivers/staging/i2o/Kconfig"
|
||||
|
||||
endif # STAGING
|
||||
|
|
|
@ -22,11 +22,11 @@ obj-$(CONFIG_VT6655) += vt6655/
|
|||
obj-$(CONFIG_VT6656) += vt6656/
|
||||
obj-$(CONFIG_VME_BUS) += vme/
|
||||
obj-$(CONFIG_IIO) += iio/
|
||||
obj-$(CONFIG_FB_SM7XX) += sm7xxfb/
|
||||
obj-$(CONFIG_FB_XGI) += xgifb/
|
||||
obj-$(CONFIG_USB_EMXX) += emxx_udc/
|
||||
obj-$(CONFIG_FT1000) += ft1000/
|
||||
obj-$(CONFIG_SPEAKUP) += speakup/
|
||||
obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_TM1217) += cptm1217/
|
||||
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/
|
||||
obj-$(CONFIG_MFD_NVEC) += nvec/
|
||||
obj-$(CONFIG_ANDROID) += android/
|
||||
|
@ -44,3 +44,5 @@ obj-$(CONFIG_GS_FPGABOOT) += gs_fpgaboot/
|
|||
obj-$(CONFIG_CRYPTO_SKEIN) += skein/
|
||||
obj-$(CONFIG_UNISYSSPAR) += unisys/
|
||||
obj-$(CONFIG_COMMON_CLK_XLNX_CLKWZRD) += clocking-wizard/
|
||||
obj-$(CONFIG_FB_TFT) += fbtft/
|
||||
obj-$(CONFIG_I2O) += i2o/
|
||||
|
|
|
@ -14,23 +14,6 @@ config ASHMEM
|
|||
It is, in theory, a good memory allocator for low-memory devices,
|
||||
because it can discard shared memory units when under memory pressure.
|
||||
|
||||
config ANDROID_LOGGER
|
||||
tristate "Android log driver"
|
||||
default n
|
||||
---help---
|
||||
This adds support for system-wide logging using four log buffers.
|
||||
|
||||
These are:
|
||||
|
||||
1: main
|
||||
2: events
|
||||
3: radio
|
||||
4: system
|
||||
|
||||
Log reading and writing is performed via normal Linux reads and
|
||||
optimized writes. This optimization avoids logging having too
|
||||
much overhead in the system.
|
||||
|
||||
config ANDROID_TIMED_OUTPUT
|
||||
bool "Timed output class driver"
|
||||
default y
|
||||
|
@ -45,15 +28,6 @@ config ANDROID_LOW_MEMORY_KILLER
|
|||
---help---
|
||||
Registers processes to be killed when memory is low
|
||||
|
||||
config ANDROID_INTF_ALARM_DEV
|
||||
tristate "Android alarm driver"
|
||||
depends on RTC_CLASS
|
||||
default n
|
||||
---help---
|
||||
Provides non-wakeup and rtc backed wakeup alarms based on rtc or
|
||||
elapsed realtime, and a non-wakeup alarm on the monotonic clock.
|
||||
Also exports the alarm interface to user-space.
|
||||
|
||||
config SYNC
|
||||
bool "Synchronization framework"
|
||||
default n
|
||||
|
|
|
@ -3,10 +3,8 @@ ccflags-y += -I$(src) # needed for trace events
|
|||
obj-y += ion/
|
||||
|
||||
obj-$(CONFIG_ASHMEM) += ashmem.o
|
||||
obj-$(CONFIG_ANDROID_LOGGER) += logger.o
|
||||
obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o
|
||||
obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o
|
||||
obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o
|
||||
obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o
|
||||
obj-$(CONFIG_SYNC) += sync.o sync_debug.o
|
||||
obj-$(CONFIG_SW_SYNC) += sw_sync.o
|
||||
|
|
|
@ -1,446 +0,0 @@
|
|||
/* drivers/rtc/alarm-dev.c
|
||||
*
|
||||
* Copyright (C) 2007-2009 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/time.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/alarmtimer.h>
|
||||
#include "android_alarm.h"
|
||||
|
||||
#define ANDROID_ALARM_PRINT_INFO (1U << 0)
|
||||
#define ANDROID_ALARM_PRINT_IO (1U << 1)
|
||||
#define ANDROID_ALARM_PRINT_INT (1U << 2)
|
||||
|
||||
static int debug_mask = ANDROID_ALARM_PRINT_INFO;
|
||||
module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
||||
|
||||
#define alarm_dbg(debug_level_mask, fmt, ...) \
|
||||
do { \
|
||||
if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) \
|
||||
pr_info(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define ANDROID_ALARM_WAKEUP_MASK ( \
|
||||
ANDROID_ALARM_RTC_WAKEUP_MASK | \
|
||||
ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK)
|
||||
|
||||
static int alarm_opened;
|
||||
static DEFINE_SPINLOCK(alarm_slock);
|
||||
static struct wakeup_source alarm_wake_lock;
|
||||
static DECLARE_WAIT_QUEUE_HEAD(alarm_wait_queue);
|
||||
static uint32_t alarm_pending;
|
||||
static uint32_t alarm_enabled;
|
||||
static uint32_t wait_pending;
|
||||
|
||||
struct devalarm {
|
||||
union {
|
||||
struct hrtimer hrt;
|
||||
struct alarm alrm;
|
||||
} u;
|
||||
enum android_alarm_type type;
|
||||
};
|
||||
|
||||
static struct devalarm alarms[ANDROID_ALARM_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* is_wakeup() - Checks to see if this alarm can wake the device
|
||||
* @type: The type of alarm being checked
|
||||
*
|
||||
* Return: 1 if this is a wakeup alarm, otherwise 0
|
||||
*/
|
||||
static int is_wakeup(enum android_alarm_type type)
|
||||
{
|
||||
return type == ANDROID_ALARM_RTC_WAKEUP ||
|
||||
type == ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP;
|
||||
}
|
||||
|
||||
static void devalarm_start(struct devalarm *alrm, ktime_t exp)
|
||||
{
|
||||
if (is_wakeup(alrm->type))
|
||||
alarm_start(&alrm->u.alrm, exp);
|
||||
else
|
||||
hrtimer_start(&alrm->u.hrt, exp, HRTIMER_MODE_ABS);
|
||||
}
|
||||
|
||||
static int devalarm_try_to_cancel(struct devalarm *alrm)
|
||||
{
|
||||
if (is_wakeup(alrm->type))
|
||||
return alarm_try_to_cancel(&alrm->u.alrm);
|
||||
return hrtimer_try_to_cancel(&alrm->u.hrt);
|
||||
}
|
||||
|
||||
static void devalarm_cancel(struct devalarm *alrm)
|
||||
{
|
||||
if (is_wakeup(alrm->type))
|
||||
alarm_cancel(&alrm->u.alrm);
|
||||
else
|
||||
hrtimer_cancel(&alrm->u.hrt);
|
||||
}
|
||||
|
||||
static void alarm_clear(enum android_alarm_type alarm_type)
|
||||
{
|
||||
uint32_t alarm_type_mask = 1U << alarm_type;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
alarm_dbg(IO, "alarm %d clear\n", alarm_type);
|
||||
devalarm_try_to_cancel(&alarms[alarm_type]);
|
||||
if (alarm_pending) {
|
||||
alarm_pending &= ~alarm_type_mask;
|
||||
if (!alarm_pending && !wait_pending)
|
||||
__pm_relax(&alarm_wake_lock);
|
||||
}
|
||||
alarm_enabled &= ~alarm_type_mask;
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
}
|
||||
|
||||
static void alarm_set(enum android_alarm_type alarm_type,
|
||||
struct timespec *ts)
|
||||
{
|
||||
uint32_t alarm_type_mask = 1U << alarm_type;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
alarm_dbg(IO, "alarm %d set %ld.%09ld\n",
|
||||
alarm_type, ts->tv_sec, ts->tv_nsec);
|
||||
alarm_enabled |= alarm_type_mask;
|
||||
devalarm_start(&alarms[alarm_type], timespec_to_ktime(*ts));
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
}
|
||||
|
||||
static int alarm_wait(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
int rv = 0;
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
alarm_dbg(IO, "alarm wait\n");
|
||||
if (!alarm_pending && wait_pending) {
|
||||
__pm_relax(&alarm_wake_lock);
|
||||
wait_pending = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
|
||||
rv = wait_event_interruptible(alarm_wait_queue, alarm_pending);
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
rv = alarm_pending;
|
||||
wait_pending = 1;
|
||||
alarm_pending = 0;
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int alarm_set_rtc(struct timespec *ts)
|
||||
{
|
||||
struct rtc_time new_rtc_tm;
|
||||
struct rtc_device *rtc_dev;
|
||||
unsigned long flags;
|
||||
int rv = 0;
|
||||
|
||||
rtc_time_to_tm(ts->tv_sec, &new_rtc_tm);
|
||||
rtc_dev = alarmtimer_get_rtcdev();
|
||||
rv = do_settimeofday(ts);
|
||||
if (rv < 0)
|
||||
return rv;
|
||||
if (rtc_dev)
|
||||
rv = rtc_set_time(rtc_dev, &new_rtc_tm);
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK;
|
||||
wake_up(&alarm_wait_queue);
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int alarm_get_time(enum android_alarm_type alarm_type,
|
||||
struct timespec *ts)
|
||||
{
|
||||
int rv = 0;
|
||||
|
||||
switch (alarm_type) {
|
||||
case ANDROID_ALARM_RTC_WAKEUP:
|
||||
case ANDROID_ALARM_RTC:
|
||||
getnstimeofday(ts);
|
||||
break;
|
||||
case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP:
|
||||
case ANDROID_ALARM_ELAPSED_REALTIME:
|
||||
get_monotonic_boottime(ts);
|
||||
break;
|
||||
case ANDROID_ALARM_SYSTEMTIME:
|
||||
ktime_get_ts(ts);
|
||||
break;
|
||||
default:
|
||||
rv = -EINVAL;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static long alarm_do_ioctl(struct file *file, unsigned int cmd,
|
||||
struct timespec *ts)
|
||||
{
|
||||
int rv = 0;
|
||||
unsigned long flags;
|
||||
enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd);
|
||||
|
||||
if (alarm_type >= ANDROID_ALARM_TYPE_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_GET_TIME(0)) {
|
||||
if ((file->f_flags & O_ACCMODE) == O_RDONLY)
|
||||
return -EPERM;
|
||||
if (file->private_data == NULL &&
|
||||
cmd != ANDROID_ALARM_SET_RTC) {
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
if (alarm_opened) {
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
return -EBUSY;
|
||||
}
|
||||
alarm_opened = 1;
|
||||
file->private_data = (void *)1;
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_CLEAR(0):
|
||||
alarm_clear(alarm_type);
|
||||
break;
|
||||
case ANDROID_ALARM_SET(0):
|
||||
alarm_set(alarm_type, ts);
|
||||
break;
|
||||
case ANDROID_ALARM_SET_AND_WAIT(0):
|
||||
alarm_set(alarm_type, ts);
|
||||
/* fall though */
|
||||
case ANDROID_ALARM_WAIT:
|
||||
rv = alarm_wait();
|
||||
break;
|
||||
case ANDROID_ALARM_SET_RTC:
|
||||
rv = alarm_set_rtc(ts);
|
||||
break;
|
||||
case ANDROID_ALARM_GET_TIME(0):
|
||||
rv = alarm_get_time(alarm_type, ts);
|
||||
break;
|
||||
|
||||
default:
|
||||
rv = -EINVAL;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
|
||||
struct timespec ts;
|
||||
int rv;
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_SET_AND_WAIT(0):
|
||||
case ANDROID_ALARM_SET(0):
|
||||
case ANDROID_ALARM_SET_RTC:
|
||||
if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = alarm_do_ioctl(file, cmd, &ts);
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_GET_TIME(0):
|
||||
if (copy_to_user((void __user *)arg, &ts, sizeof(ts)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long alarm_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
|
||||
struct timespec ts;
|
||||
int rv;
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_SET_AND_WAIT_COMPAT(0):
|
||||
case ANDROID_ALARM_SET_COMPAT(0):
|
||||
case ANDROID_ALARM_SET_RTC_COMPAT:
|
||||
if (compat_get_timespec(&ts, (void __user *)arg))
|
||||
return -EFAULT;
|
||||
/* fall through */
|
||||
case ANDROID_ALARM_GET_TIME_COMPAT(0):
|
||||
cmd = ANDROID_ALARM_COMPAT_TO_NORM(cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
rv = alarm_do_ioctl(file, cmd, &ts);
|
||||
if (rv)
|
||||
return rv;
|
||||
|
||||
switch (ANDROID_ALARM_BASE_CMD(cmd)) {
|
||||
case ANDROID_ALARM_GET_TIME(0): /* NOTE: we modified cmd above */
|
||||
if (compat_put_timespec(&ts, (void __user *)arg))
|
||||
return -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int alarm_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alarm_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
if (file->private_data) {
|
||||
for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) {
|
||||
uint32_t alarm_type_mask = 1U << i;
|
||||
|
||||
if (alarm_enabled & alarm_type_mask) {
|
||||
alarm_dbg(INFO,
|
||||
"%s: clear alarm, pending %d\n",
|
||||
__func__,
|
||||
!!(alarm_pending & alarm_type_mask));
|
||||
alarm_enabled &= ~alarm_type_mask;
|
||||
}
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
devalarm_cancel(&alarms[i]);
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
}
|
||||
if (alarm_pending | wait_pending) {
|
||||
if (alarm_pending)
|
||||
alarm_dbg(INFO, "%s: clear pending alarms %x\n",
|
||||
__func__, alarm_pending);
|
||||
__pm_relax(&alarm_wake_lock);
|
||||
wait_pending = 0;
|
||||
alarm_pending = 0;
|
||||
}
|
||||
alarm_opened = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void devalarm_triggered(struct devalarm *alarm)
|
||||
{
|
||||
unsigned long flags;
|
||||
uint32_t alarm_type_mask = 1U << alarm->type;
|
||||
|
||||
alarm_dbg(INT, "%s: type %d\n", __func__, alarm->type);
|
||||
spin_lock_irqsave(&alarm_slock, flags);
|
||||
if (alarm_enabled & alarm_type_mask) {
|
||||
__pm_wakeup_event(&alarm_wake_lock, 5000); /* 5secs */
|
||||
alarm_enabled &= ~alarm_type_mask;
|
||||
alarm_pending |= alarm_type_mask;
|
||||
wake_up(&alarm_wait_queue);
|
||||
}
|
||||
spin_unlock_irqrestore(&alarm_slock, flags);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart devalarm_hrthandler(struct hrtimer *hrt)
|
||||
{
|
||||
struct devalarm *devalrm = container_of(hrt, struct devalarm, u.hrt);
|
||||
|
||||
devalarm_triggered(devalrm);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
static enum alarmtimer_restart devalarm_alarmhandler(struct alarm *alrm,
|
||||
ktime_t now)
|
||||
{
|
||||
struct devalarm *devalrm = container_of(alrm, struct devalarm, u.alrm);
|
||||
|
||||
devalarm_triggered(devalrm);
|
||||
return ALARMTIMER_NORESTART;
|
||||
}
|
||||
|
||||
|
||||
static const struct file_operations alarm_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = alarm_ioctl,
|
||||
.open = alarm_open,
|
||||
.release = alarm_release,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = alarm_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct miscdevice alarm_device = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "alarm",
|
||||
.fops = &alarm_fops,
|
||||
};
|
||||
|
||||
static int __init alarm_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
int i;
|
||||
|
||||
err = misc_register(&alarm_device);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
alarm_init(&alarms[ANDROID_ALARM_RTC_WAKEUP].u.alrm,
|
||||
ALARM_REALTIME, devalarm_alarmhandler);
|
||||
hrtimer_init(&alarms[ANDROID_ALARM_RTC].u.hrt,
|
||||
CLOCK_REALTIME, HRTIMER_MODE_ABS);
|
||||
alarm_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].u.alrm,
|
||||
ALARM_BOOTTIME, devalarm_alarmhandler);
|
||||
hrtimer_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME].u.hrt,
|
||||
CLOCK_BOOTTIME, HRTIMER_MODE_ABS);
|
||||
hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].u.hrt,
|
||||
CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
|
||||
|
||||
for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) {
|
||||
alarms[i].type = i;
|
||||
if (!is_wakeup(i))
|
||||
alarms[i].u.hrt.function = devalarm_hrthandler;
|
||||
}
|
||||
|
||||
wakeup_source_init(&alarm_wake_lock, "alarm");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alarm_dev_exit(void)
|
||||
{
|
||||
misc_deregister(&alarm_device);
|
||||
wakeup_source_trash(&alarm_wake_lock);
|
||||
}
|
||||
|
||||
module_init(alarm_dev_init);
|
||||
module_exit(alarm_dev_exit);
|
||||
MODULE_LICENSE("GPL");
|
|
@ -1,41 +0,0 @@
|
|||
/* include/linux/android_alarm.h
|
||||
*
|
||||
* Copyright (C) 2006-2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_ANDROID_ALARM_H
|
||||
#define _LINUX_ANDROID_ALARM_H
|
||||
|
||||
#include <linux/compat.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#include "uapi/android_alarm.h"
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#define ANDROID_ALARM_SET_COMPAT(type) ALARM_IOW(2, type, \
|
||||
struct compat_timespec)
|
||||
#define ANDROID_ALARM_SET_AND_WAIT_COMPAT(type) ALARM_IOW(3, type, \
|
||||
struct compat_timespec)
|
||||
#define ANDROID_ALARM_GET_TIME_COMPAT(type) ALARM_IOW(4, type, \
|
||||
struct compat_timespec)
|
||||
#define ANDROID_ALARM_SET_RTC_COMPAT _IOW('a', 5, \
|
||||
struct compat_timespec)
|
||||
#define ANDROID_ALARM_IOCTL_NR(cmd) (_IOC_NR(cmd) & ((1<<4)-1))
|
||||
#define ANDROID_ALARM_COMPAT_TO_NORM(cmd) \
|
||||
ALARM_IOW(ANDROID_ALARM_IOCTL_NR(cmd), \
|
||||
ANDROID_ALARM_IOCTL_TO_TYPE(cmd), \
|
||||
struct timespec)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -447,8 +447,8 @@ ashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc)
|
|||
loff_t end = (range->pgend + 1) * PAGE_SIZE;
|
||||
|
||||
vfs_fallocate(range->asma->file,
|
||||
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
||||
start, end - start);
|
||||
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
||||
start, end - start);
|
||||
range->purged = ASHMEM_WAS_PURGED;
|
||||
lru_del(range);
|
||||
|
||||
|
@ -549,7 +549,6 @@ static int get_name(struct ashmem_area *asma, void __user *name)
|
|||
|
||||
mutex_lock(&ashmem_mutex);
|
||||
if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') {
|
||||
|
||||
/*
|
||||
* Copying only `len', instead of ASHMEM_NAME_LEN, bytes
|
||||
* prevents us from revealing one user's stack to another.
|
||||
|
@ -751,10 +750,10 @@ static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
|
||||
switch (cmd) {
|
||||
case ASHMEM_SET_NAME:
|
||||
ret = set_name(asma, (void __user *) arg);
|
||||
ret = set_name(asma, (void __user *)arg);
|
||||
break;
|
||||
case ASHMEM_GET_NAME:
|
||||
ret = get_name(asma, (void __user *) arg);
|
||||
ret = get_name(asma, (void __user *)arg);
|
||||
break;
|
||||
case ASHMEM_SET_SIZE:
|
||||
ret = -EINVAL;
|
||||
|
@ -775,7 +774,7 @@ static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
case ASHMEM_PIN:
|
||||
case ASHMEM_UNPIN:
|
||||
case ASHMEM_GET_PIN_STATUS:
|
||||
ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
|
||||
ret = ashmem_pin_unpin(asma, cmd, (void __user *)arg);
|
||||
break;
|
||||
case ASHMEM_PURGE_ALL_CACHES:
|
||||
ret = -EPERM;
|
||||
|
@ -798,7 +797,6 @@ static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
static long compat_ashmem_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
|
||||
switch (cmd) {
|
||||
case COMPAT_ASHMEM_SET_SIZE:
|
||||
cmd = ASHMEM_SET_SIZE;
|
||||
|
|
|
@ -1508,6 +1508,9 @@ void ion_device_add_heap(struct ion_device *dev, struct ion_heap *heap)
|
|||
pr_err("%s: can not add heap with invalid ops struct.\n",
|
||||
__func__);
|
||||
|
||||
spin_lock_init(&heap->free_lock);
|
||||
heap->free_list_size = 0;
|
||||
|
||||
if (heap->flags & ION_HEAP_FLAG_DEFER_FREE)
|
||||
ion_heap_init_deferred_free(heap);
|
||||
|
||||
|
|
|
@ -39,24 +39,6 @@ struct ion_cma_buffer_info {
|
|||
struct sg_table *table;
|
||||
};
|
||||
|
||||
/*
|
||||
* Create scatter-list for the already allocated DMA buffer.
|
||||
* This function could be replaced by dma_common_get_sgtable
|
||||
* as soon as it will avalaible.
|
||||
*/
|
||||
static int ion_cma_get_sgtable(struct device *dev, struct sg_table *sgt,
|
||||
void *cpu_addr, dma_addr_t handle, size_t size)
|
||||
{
|
||||
struct page *page = virt_to_page(cpu_addr);
|
||||
int ret;
|
||||
|
||||
ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
|
||||
sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ION CMA heap operations functions */
|
||||
static int ion_cma_allocate(struct ion_heap *heap, struct ion_buffer *buffer,
|
||||
|
@ -91,7 +73,7 @@ static int ion_cma_allocate(struct ion_heap *heap, struct ion_buffer *buffer,
|
|||
if (!info->table)
|
||||
goto free_mem;
|
||||
|
||||
if (ion_cma_get_sgtable
|
||||
if (dma_common_get_sgtable
|
||||
(dev, info->table, info->cpu_addr, info->handle, len))
|
||||
goto free_table;
|
||||
/* keep this for memory release */
|
||||
|
|
|
@ -253,8 +253,6 @@ int ion_heap_init_deferred_free(struct ion_heap *heap)
|
|||
struct sched_param param = { .sched_priority = 0 };
|
||||
|
||||
INIT_LIST_HEAD(&heap->free_list);
|
||||
heap->free_list_size = 0;
|
||||
spin_lock_init(&heap->free_lock);
|
||||
init_waitqueue_head(&heap->waitqueue);
|
||||
heap->task = kthread_run(ion_heap_deferred_free, heap,
|
||||
"%s", heap->name);
|
||||
|
|
|
@ -1,808 +0,0 @@
|
|||
/*
|
||||
* drivers/misc/logger.c
|
||||
*
|
||||
* A Logging Subsystem
|
||||
*
|
||||
* Copyright (C) 2007-2008 Google, Inc.
|
||||
*
|
||||
* Robert Love <rlove@google.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "logger: " fmt
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/aio.h>
|
||||
#include "logger.h"
|
||||
|
||||
#include <asm/ioctls.h>
|
||||
|
||||
/**
|
||||
* struct logger_log - represents a specific log, such as 'main' or 'radio'
|
||||
* @buffer: The actual ring buffer
|
||||
* @misc: The "misc" device representing the log
|
||||
* @wq: The wait queue for @readers
|
||||
* @readers: This log's readers
|
||||
* @mutex: The mutex that protects the @buffer
|
||||
* @w_off: The current write head offset
|
||||
* @head: The head, or location that readers start reading at.
|
||||
* @size: The size of the log
|
||||
* @logs: The list of log channels
|
||||
*
|
||||
* This structure lives from module insertion until module removal, so it does
|
||||
* not need additional reference counting. The structure is protected by the
|
||||
* mutex 'mutex'.
|
||||
*/
|
||||
struct logger_log {
|
||||
unsigned char *buffer;
|
||||
struct miscdevice misc;
|
||||
wait_queue_head_t wq;
|
||||
struct list_head readers;
|
||||
struct mutex mutex;
|
||||
size_t w_off;
|
||||
size_t head;
|
||||
size_t size;
|
||||
struct list_head logs;
|
||||
};
|
||||
|
||||
static LIST_HEAD(log_list);
|
||||
|
||||
|
||||
/**
|
||||
* struct logger_reader - a logging device open for reading
|
||||
* @log: The associated log
|
||||
* @list: The associated entry in @logger_log's list
|
||||
* @r_off: The current read head offset.
|
||||
* @r_all: Reader can read all entries
|
||||
* @r_ver: Reader ABI version
|
||||
*
|
||||
* This object lives from open to release, so we don't need additional
|
||||
* reference counting. The structure is protected by log->mutex.
|
||||
*/
|
||||
struct logger_reader {
|
||||
struct logger_log *log;
|
||||
struct list_head list;
|
||||
size_t r_off;
|
||||
bool r_all;
|
||||
int r_ver;
|
||||
};
|
||||
|
||||
/* logger_offset - returns index 'n' into the log via (optimized) modulus */
|
||||
static size_t logger_offset(struct logger_log *log, size_t n)
|
||||
{
|
||||
return n & (log->size - 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* file_get_log - Given a file structure, return the associated log
|
||||
*
|
||||
* This isn't aesthetic. We have several goals:
|
||||
*
|
||||
* 1) Need to quickly obtain the associated log during an I/O operation
|
||||
* 2) Readers need to maintain state (logger_reader)
|
||||
* 3) Writers need to be very fast (open() should be a near no-op)
|
||||
*
|
||||
* In the reader case, we can trivially go file->logger_reader->logger_log.
|
||||
* For a writer, we don't want to maintain a logger_reader, so we just go
|
||||
* file->logger_log. Thus what file->private_data points at depends on whether
|
||||
* or not the file was opened for reading. This function hides that dirtiness.
|
||||
*/
|
||||
static inline struct logger_log *file_get_log(struct file *file)
|
||||
{
|
||||
if (file->f_mode & FMODE_READ) {
|
||||
struct logger_reader *reader = file->private_data;
|
||||
|
||||
return reader->log;
|
||||
}
|
||||
return file->private_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_entry_header - returns a pointer to the logger_entry header within
|
||||
* 'log' starting at offset 'off'. A temporary logger_entry 'scratch' must
|
||||
* be provided. Typically the return value will be a pointer within
|
||||
* 'logger->buf'. However, a pointer to 'scratch' may be returned if
|
||||
* the log entry spans the end and beginning of the circular buffer.
|
||||
*/
|
||||
static struct logger_entry *get_entry_header(struct logger_log *log,
|
||||
size_t off, struct logger_entry *scratch)
|
||||
{
|
||||
size_t len = min(sizeof(struct logger_entry), log->size - off);
|
||||
|
||||
if (len != sizeof(struct logger_entry)) {
|
||||
memcpy(((void *) scratch), log->buffer + off, len);
|
||||
memcpy(((void *) scratch) + len, log->buffer,
|
||||
sizeof(struct logger_entry) - len);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
return (struct logger_entry *) (log->buffer + off);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_entry_msg_len - Grabs the length of the message of the entry
|
||||
* starting from from 'off'.
|
||||
*
|
||||
* An entry length is 2 bytes (16 bits) in host endian order.
|
||||
* In the log, the length does not include the size of the log entry structure.
|
||||
* This function returns the size including the log entry structure.
|
||||
*
|
||||
* Caller needs to hold log->mutex.
|
||||
*/
|
||||
static __u32 get_entry_msg_len(struct logger_log *log, size_t off)
|
||||
{
|
||||
struct logger_entry scratch;
|
||||
struct logger_entry *entry;
|
||||
|
||||
entry = get_entry_header(log, off, &scratch);
|
||||
return entry->len;
|
||||
}
|
||||
|
||||
static size_t get_user_hdr_len(int ver)
|
||||
{
|
||||
if (ver < 2)
|
||||
return sizeof(struct user_logger_entry_compat);
|
||||
return sizeof(struct logger_entry);
|
||||
}
|
||||
|
||||
static ssize_t copy_header_to_user(int ver, struct logger_entry *entry,
|
||||
char __user *buf)
|
||||
{
|
||||
void *hdr;
|
||||
size_t hdr_len;
|
||||
struct user_logger_entry_compat v1;
|
||||
|
||||
if (ver < 2) {
|
||||
v1.len = entry->len;
|
||||
v1.__pad = 0;
|
||||
v1.pid = entry->pid;
|
||||
v1.tid = entry->tid;
|
||||
v1.sec = entry->sec;
|
||||
v1.nsec = entry->nsec;
|
||||
hdr = &v1;
|
||||
hdr_len = sizeof(struct user_logger_entry_compat);
|
||||
} else {
|
||||
hdr = entry;
|
||||
hdr_len = sizeof(struct logger_entry);
|
||||
}
|
||||
|
||||
return copy_to_user(buf, hdr, hdr_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* do_read_log_to_user - reads exactly 'count' bytes from 'log' into the
|
||||
* user-space buffer 'buf'. Returns 'count' on success.
|
||||
*
|
||||
* Caller must hold log->mutex.
|
||||
*/
|
||||
static ssize_t do_read_log_to_user(struct logger_log *log,
|
||||
struct logger_reader *reader,
|
||||
char __user *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct logger_entry scratch;
|
||||
struct logger_entry *entry;
|
||||
size_t len;
|
||||
size_t msg_start;
|
||||
|
||||
/*
|
||||
* First, copy the header to userspace, using the version of
|
||||
* the header requested
|
||||
*/
|
||||
entry = get_entry_header(log, reader->r_off, &scratch);
|
||||
if (copy_header_to_user(reader->r_ver, entry, buf))
|
||||
return -EFAULT;
|
||||
|
||||
count -= get_user_hdr_len(reader->r_ver);
|
||||
buf += get_user_hdr_len(reader->r_ver);
|
||||
msg_start = logger_offset(log,
|
||||
reader->r_off + sizeof(struct logger_entry));
|
||||
|
||||
/*
|
||||
* We read from the msg in two disjoint operations. First, we read from
|
||||
* the current msg head offset up to 'count' bytes or to the end of
|
||||
* the log, whichever comes first.
|
||||
*/
|
||||
len = min(count, log->size - msg_start);
|
||||
if (copy_to_user(buf, log->buffer + msg_start, len))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Second, we read any remaining bytes, starting back at the head of
|
||||
* the log.
|
||||
*/
|
||||
if (count != len)
|
||||
if (copy_to_user(buf + len, log->buffer, count - len))
|
||||
return -EFAULT;
|
||||
|
||||
reader->r_off = logger_offset(log, reader->r_off +
|
||||
sizeof(struct logger_entry) + count);
|
||||
|
||||
return count + get_user_hdr_len(reader->r_ver);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_next_entry_by_uid - Starting at 'off', returns an offset into
|
||||
* 'log->buffer' which contains the first entry readable by 'euid'
|
||||
*/
|
||||
static size_t get_next_entry_by_uid(struct logger_log *log,
|
||||
size_t off, kuid_t euid)
|
||||
{
|
||||
while (off != log->w_off) {
|
||||
struct logger_entry *entry;
|
||||
struct logger_entry scratch;
|
||||
size_t next_len;
|
||||
|
||||
entry = get_entry_header(log, off, &scratch);
|
||||
|
||||
if (uid_eq(entry->euid, euid))
|
||||
return off;
|
||||
|
||||
next_len = sizeof(struct logger_entry) + entry->len;
|
||||
off = logger_offset(log, off + next_len);
|
||||
}
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
/*
|
||||
* logger_read - our log's read() method
|
||||
*
|
||||
* Behavior:
|
||||
*
|
||||
* - O_NONBLOCK works
|
||||
* - If there are no log entries to read, blocks until log is written to
|
||||
* - Atomically reads exactly one log entry
|
||||
*
|
||||
* Will set errno to EINVAL if read
|
||||
* buffer is insufficient to hold next entry.
|
||||
*/
|
||||
static ssize_t logger_read(struct file *file, char __user *buf,
|
||||
size_t count, loff_t *pos)
|
||||
{
|
||||
struct logger_reader *reader = file->private_data;
|
||||
struct logger_log *log = reader->log;
|
||||
ssize_t ret;
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
start:
|
||||
while (1) {
|
||||
mutex_lock(&log->mutex);
|
||||
|
||||
prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);
|
||||
|
||||
ret = (log->w_off == reader->r_off);
|
||||
mutex_unlock(&log->mutex);
|
||||
if (!ret)
|
||||
break;
|
||||
|
||||
if (file->f_flags & O_NONBLOCK) {
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (signal_pending(current)) {
|
||||
ret = -EINTR;
|
||||
break;
|
||||
}
|
||||
|
||||
schedule();
|
||||
}
|
||||
|
||||
finish_wait(&log->wq, &wait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
|
||||
if (!reader->r_all)
|
||||
reader->r_off = get_next_entry_by_uid(log,
|
||||
reader->r_off, current_euid());
|
||||
|
||||
/* is there still something to read or did we race? */
|
||||
if (unlikely(log->w_off == reader->r_off)) {
|
||||
mutex_unlock(&log->mutex);
|
||||
goto start;
|
||||
}
|
||||
|
||||
/* get the size of the next entry */
|
||||
ret = get_user_hdr_len(reader->r_ver) +
|
||||
get_entry_msg_len(log, reader->r_off);
|
||||
if (count < ret) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* get exactly one entry from the log */
|
||||
ret = do_read_log_to_user(log, reader, buf, ret);
|
||||
|
||||
out:
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_next_entry - return the offset of the first valid entry at least 'len'
|
||||
* bytes after 'off'.
|
||||
*
|
||||
* Caller must hold log->mutex.
|
||||
*/
|
||||
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
do {
|
||||
size_t nr = sizeof(struct logger_entry) +
|
||||
get_entry_msg_len(log, off);
|
||||
off = logger_offset(log, off + nr);
|
||||
count += nr;
|
||||
} while (count < len);
|
||||
|
||||
return off;
|
||||
}
|
||||
|
||||
/*
|
||||
* is_between - is a < c < b, accounting for wrapping of a, b, and c
|
||||
* positions in the buffer
|
||||
*
|
||||
* That is, if a<b, check for c between a and b
|
||||
* and if a>b, check for c outside (not between) a and b
|
||||
*
|
||||
* |------- a xxxxxxxx b --------|
|
||||
* c^
|
||||
*
|
||||
* |xxxxx b --------- a xxxxxxxxx|
|
||||
* c^
|
||||
* or c^
|
||||
*/
|
||||
static inline int is_between(size_t a, size_t b, size_t c)
|
||||
{
|
||||
if (a < b) {
|
||||
/* is c between a and b? */
|
||||
if (a < c && c <= b)
|
||||
return 1;
|
||||
} else {
|
||||
/* is c outside of b through a? */
|
||||
if (c <= b || a < c)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* fix_up_readers - walk the list of all readers and "fix up" any who were
|
||||
* lapped by the writer; also do the same for the default "start head".
|
||||
* We do this by "pulling forward" the readers and start head to the first
|
||||
* entry after the new write head.
|
||||
*
|
||||
* The caller needs to hold log->mutex.
|
||||
*/
|
||||
static void fix_up_readers(struct logger_log *log, size_t len)
|
||||
{
|
||||
size_t old = log->w_off;
|
||||
size_t new = logger_offset(log, old + len);
|
||||
struct logger_reader *reader;
|
||||
|
||||
if (is_between(old, new, log->head))
|
||||
log->head = get_next_entry(log, log->head, len);
|
||||
|
||||
list_for_each_entry(reader, &log->readers, list)
|
||||
if (is_between(old, new, reader->r_off))
|
||||
reader->r_off = get_next_entry(log, reader->r_off, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* logger_write_iter - our write method, implementing support for write(),
|
||||
* writev(), and aio_write(). Writes are our fast path, and we try to optimize
|
||||
* them above all else.
|
||||
*/
|
||||
static ssize_t logger_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
{
|
||||
struct logger_log *log = file_get_log(iocb->ki_filp);
|
||||
struct logger_entry header;
|
||||
struct timespec now;
|
||||
size_t len, count, w_off;
|
||||
|
||||
count = min_t(size_t, iocb->ki_nbytes, LOGGER_ENTRY_MAX_PAYLOAD);
|
||||
|
||||
now = current_kernel_time();
|
||||
|
||||
header.pid = current->tgid;
|
||||
header.tid = current->pid;
|
||||
header.sec = now.tv_sec;
|
||||
header.nsec = now.tv_nsec;
|
||||
header.euid = current_euid();
|
||||
header.len = count;
|
||||
header.hdr_size = sizeof(struct logger_entry);
|
||||
|
||||
/* null writes succeed, return zero */
|
||||
if (unlikely(!header.len))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
|
||||
/*
|
||||
* Fix up any readers, pulling them forward to the first readable
|
||||
* entry after (what will be) the new write offset. We do this now
|
||||
* because if we partially fail, we can end up with clobbered log
|
||||
* entries that encroach on readable buffer.
|
||||
*/
|
||||
fix_up_readers(log, sizeof(struct logger_entry) + header.len);
|
||||
|
||||
len = min(sizeof(header), log->size - log->w_off);
|
||||
memcpy(log->buffer + log->w_off, &header, len);
|
||||
memcpy(log->buffer, (char *)&header + len, sizeof(header) - len);
|
||||
|
||||
/* Work with a copy until we are ready to commit the whole entry */
|
||||
w_off = logger_offset(log, log->w_off + sizeof(struct logger_entry));
|
||||
|
||||
len = min(count, log->size - w_off);
|
||||
|
||||
if (copy_from_iter(log->buffer + w_off, len, from) != len) {
|
||||
/*
|
||||
* Note that by not updating log->w_off, this abandons the
|
||||
* portion of the new entry that *was* successfully
|
||||
* copied, just above. This is intentional to avoid
|
||||
* message corruption from missing fragments.
|
||||
*/
|
||||
mutex_unlock(&log->mutex);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (copy_from_iter(log->buffer, count - len, from) != count - len) {
|
||||
mutex_unlock(&log->mutex);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
log->w_off = logger_offset(log, w_off + count);
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
/* wake up any blocked readers */
|
||||
wake_up_interruptible(&log->wq);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct logger_log *get_log_from_minor(int minor)
|
||||
{
|
||||
struct logger_log *log;
|
||||
|
||||
list_for_each_entry(log, &log_list, logs)
|
||||
if (log->misc.minor == minor)
|
||||
return log;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* logger_open - the log's open() file operation
|
||||
*
|
||||
* Note how near a no-op this is in the write-only case. Keep it that way!
|
||||
*/
|
||||
static int logger_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct logger_log *log;
|
||||
int ret;
|
||||
|
||||
ret = nonseekable_open(inode, file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
log = get_log_from_minor(MINOR(inode->i_rdev));
|
||||
if (!log)
|
||||
return -ENODEV;
|
||||
|
||||
if (file->f_mode & FMODE_READ) {
|
||||
struct logger_reader *reader;
|
||||
|
||||
reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);
|
||||
if (!reader)
|
||||
return -ENOMEM;
|
||||
|
||||
reader->log = log;
|
||||
reader->r_ver = 1;
|
||||
reader->r_all = in_egroup_p(inode->i_gid) ||
|
||||
capable(CAP_SYSLOG);
|
||||
|
||||
INIT_LIST_HEAD(&reader->list);
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
reader->r_off = log->head;
|
||||
list_add_tail(&reader->list, &log->readers);
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
file->private_data = reader;
|
||||
} else
|
||||
file->private_data = log;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* logger_release - the log's release file operation
|
||||
*
|
||||
* Note this is a total no-op in the write-only case. Keep it that way!
|
||||
*/
|
||||
static int logger_release(struct inode *ignored, struct file *file)
|
||||
{
|
||||
if (file->f_mode & FMODE_READ) {
|
||||
struct logger_reader *reader = file->private_data;
|
||||
struct logger_log *log = reader->log;
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
list_del(&reader->list);
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
kfree(reader);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* logger_poll - the log's poll file operation, for poll/select/epoll
|
||||
*
|
||||
* Note we always return POLLOUT, because you can always write() to the log.
|
||||
* Note also that, strictly speaking, a return value of POLLIN does not
|
||||
* guarantee that the log is readable without blocking, as there is a small
|
||||
* chance that the writer can lap the reader in the interim between poll()
|
||||
* returning and the read() request.
|
||||
*/
|
||||
static unsigned int logger_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct logger_reader *reader;
|
||||
struct logger_log *log;
|
||||
unsigned int ret = POLLOUT | POLLWRNORM;
|
||||
|
||||
if (!(file->f_mode & FMODE_READ))
|
||||
return ret;
|
||||
|
||||
reader = file->private_data;
|
||||
log = reader->log;
|
||||
|
||||
poll_wait(file, &log->wq, wait);
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
if (!reader->r_all)
|
||||
reader->r_off = get_next_entry_by_uid(log,
|
||||
reader->r_off, current_euid());
|
||||
|
||||
if (log->w_off != reader->r_off)
|
||||
ret |= POLLIN | POLLRDNORM;
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long logger_set_version(struct logger_reader *reader, void __user *arg)
|
||||
{
|
||||
int version;
|
||||
|
||||
if (copy_from_user(&version, arg, sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
if ((version < 1) || (version > 2))
|
||||
return -EINVAL;
|
||||
|
||||
reader->r_ver = version;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct logger_log *log = file_get_log(file);
|
||||
struct logger_reader *reader;
|
||||
long ret = -EINVAL;
|
||||
void __user *argp = (void __user *) arg;
|
||||
|
||||
mutex_lock(&log->mutex);
|
||||
|
||||
switch (cmd) {
|
||||
case LOGGER_GET_LOG_BUF_SIZE:
|
||||
ret = log->size;
|
||||
break;
|
||||
case LOGGER_GET_LOG_LEN:
|
||||
if (!(file->f_mode & FMODE_READ)) {
|
||||
ret = -EBADF;
|
||||
break;
|
||||
}
|
||||
reader = file->private_data;
|
||||
if (log->w_off >= reader->r_off)
|
||||
ret = log->w_off - reader->r_off;
|
||||
else
|
||||
ret = (log->size - reader->r_off) + log->w_off;
|
||||
break;
|
||||
case LOGGER_GET_NEXT_ENTRY_LEN:
|
||||
if (!(file->f_mode & FMODE_READ)) {
|
||||
ret = -EBADF;
|
||||
break;
|
||||
}
|
||||
reader = file->private_data;
|
||||
|
||||
if (!reader->r_all)
|
||||
reader->r_off = get_next_entry_by_uid(log,
|
||||
reader->r_off, current_euid());
|
||||
|
||||
if (log->w_off != reader->r_off)
|
||||
ret = get_user_hdr_len(reader->r_ver) +
|
||||
get_entry_msg_len(log, reader->r_off);
|
||||
else
|
||||
ret = 0;
|
||||
break;
|
||||
case LOGGER_FLUSH_LOG:
|
||||
if (!(file->f_mode & FMODE_WRITE)) {
|
||||
ret = -EBADF;
|
||||
break;
|
||||
}
|
||||
if (!(in_egroup_p(file_inode(file)->i_gid) ||
|
||||
capable(CAP_SYSLOG))) {
|
||||
ret = -EPERM;
|
||||
break;
|
||||
}
|
||||
list_for_each_entry(reader, &log->readers, list)
|
||||
reader->r_off = log->w_off;
|
||||
log->head = log->w_off;
|
||||
ret = 0;
|
||||
break;
|
||||
case LOGGER_GET_VERSION:
|
||||
if (!(file->f_mode & FMODE_READ)) {
|
||||
ret = -EBADF;
|
||||
break;
|
||||
}
|
||||
reader = file->private_data;
|
||||
ret = reader->r_ver;
|
||||
break;
|
||||
case LOGGER_SET_VERSION:
|
||||
if (!(file->f_mode & FMODE_READ)) {
|
||||
ret = -EBADF;
|
||||
break;
|
||||
}
|
||||
reader = file->private_data;
|
||||
ret = logger_set_version(reader, argp);
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&log->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations logger_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = logger_read,
|
||||
.write_iter = logger_write_iter,
|
||||
.poll = logger_poll,
|
||||
.unlocked_ioctl = logger_ioctl,
|
||||
.compat_ioctl = logger_ioctl,
|
||||
.open = logger_open,
|
||||
.release = logger_release,
|
||||
};
|
||||
|
||||
/*
|
||||
* Log size must must be a power of two, and greater than
|
||||
* (LOGGER_ENTRY_MAX_PAYLOAD + sizeof(struct logger_entry)).
|
||||
*/
|
||||
static int __init create_log(char *log_name, int size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct logger_log *log;
|
||||
unsigned char *buffer;
|
||||
|
||||
buffer = vmalloc(size);
|
||||
if (buffer == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);
|
||||
if (log == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free_buffer;
|
||||
}
|
||||
log->buffer = buffer;
|
||||
|
||||
log->misc.minor = MISC_DYNAMIC_MINOR;
|
||||
log->misc.name = kstrdup(log_name, GFP_KERNEL);
|
||||
if (log->misc.name == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free_log;
|
||||
}
|
||||
|
||||
log->misc.fops = &logger_fops;
|
||||
log->misc.parent = NULL;
|
||||
|
||||
init_waitqueue_head(&log->wq);
|
||||
INIT_LIST_HEAD(&log->readers);
|
||||
mutex_init(&log->mutex);
|
||||
log->w_off = 0;
|
||||
log->head = 0;
|
||||
log->size = size;
|
||||
|
||||
INIT_LIST_HEAD(&log->logs);
|
||||
list_add_tail(&log->logs, &log_list);
|
||||
|
||||
/* finally, initialize the misc device for this log */
|
||||
ret = misc_register(&log->misc);
|
||||
if (unlikely(ret)) {
|
||||
pr_err("failed to register misc device for log '%s'!\n",
|
||||
log->misc.name);
|
||||
goto out_free_misc_name;
|
||||
}
|
||||
|
||||
pr_info("created %luK log '%s'\n",
|
||||
(unsigned long) log->size >> 10, log->misc.name);
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_misc_name:
|
||||
kfree(log->misc.name);
|
||||
|
||||
out_free_log:
|
||||
kfree(log);
|
||||
|
||||
out_free_buffer:
|
||||
vfree(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init logger_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = create_log(LOGGER_LOG_MAIN, 256*1024);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
|
||||
ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
|
||||
ret = create_log(LOGGER_LOG_RADIO, 256*1024);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
|
||||
ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit logger_exit(void)
|
||||
{
|
||||
struct logger_log *current_log, *next_log;
|
||||
|
||||
list_for_each_entry_safe(current_log, next_log, &log_list, logs) {
|
||||
/* we have to delete all the entry inside log_list */
|
||||
misc_deregister(¤t_log->misc);
|
||||
vfree(current_log->buffer);
|
||||
kfree(current_log->misc.name);
|
||||
list_del(¤t_log->logs);
|
||||
kfree(current_log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
device_initcall(logger_init);
|
||||
module_exit(logger_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Robert Love, <rlove@google.com>");
|
||||
MODULE_DESCRIPTION("Android Logger");
|
|
@ -1,89 +0,0 @@
|
|||
/* include/linux/logger.h
|
||||
*
|
||||
* Copyright (C) 2007-2008 Google, Inc.
|
||||
* Author: Robert Love <rlove@android.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_LOGGER_H
|
||||
#define _LINUX_LOGGER_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
/**
|
||||
* struct user_logger_entry_compat - defines a single entry that is given to a logger
|
||||
* @len: The length of the payload
|
||||
* @__pad: Two bytes of padding that appear to be required
|
||||
* @pid: The generating process' process ID
|
||||
* @tid: The generating process' thread ID
|
||||
* @sec: The number of seconds that have elapsed since the Epoch
|
||||
* @nsec: The number of nanoseconds that have elapsed since @sec
|
||||
* @msg: The message that is to be logged
|
||||
*
|
||||
* The userspace structure for version 1 of the logger_entry ABI.
|
||||
* This structure is returned to userspace unless the caller requests
|
||||
* an upgrade to a newer ABI version.
|
||||
*/
|
||||
struct user_logger_entry_compat {
|
||||
__u16 len;
|
||||
__u16 __pad;
|
||||
__s32 pid;
|
||||
__s32 tid;
|
||||
__s32 sec;
|
||||
__s32 nsec;
|
||||
char msg[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct logger_entry - defines a single entry that is given to a logger
|
||||
* @len: The length of the payload
|
||||
* @hdr_size: sizeof(struct logger_entry_v2)
|
||||
* @pid: The generating process' process ID
|
||||
* @tid: The generating process' thread ID
|
||||
* @sec: The number of seconds that have elapsed since the Epoch
|
||||
* @nsec: The number of nanoseconds that have elapsed since @sec
|
||||
* @euid: Effective UID of logger
|
||||
* @msg: The message that is to be logged
|
||||
*
|
||||
* The structure for version 2 of the logger_entry ABI.
|
||||
* This structure is returned to userspace if ioctl(LOGGER_SET_VERSION)
|
||||
* is called with version >= 2
|
||||
*/
|
||||
struct logger_entry {
|
||||
__u16 len;
|
||||
__u16 hdr_size;
|
||||
__s32 pid;
|
||||
__s32 tid;
|
||||
__s32 sec;
|
||||
__s32 nsec;
|
||||
kuid_t euid;
|
||||
char msg[0];
|
||||
};
|
||||
|
||||
#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
|
||||
#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
|
||||
#define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */
|
||||
#define LOGGER_LOG_MAIN "log_main" /* everything else */
|
||||
|
||||
#define LOGGER_ENTRY_MAX_PAYLOAD 4076
|
||||
|
||||
#define __LOGGERIO 0xAE
|
||||
|
||||
#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */
|
||||
#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
|
||||
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
|
||||
#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */
|
||||
#define LOGGER_GET_VERSION _IO(__LOGGERIO, 5) /* abi version */
|
||||
#define LOGGER_SET_VERSION _IO(__LOGGERIO, 6) /* abi version */
|
||||
|
||||
#endif /* _LINUX_LOGGER_H */
|
|
@ -96,7 +96,8 @@ static void sync_print_pt(struct seq_file *s, struct sync_pt *pt, bool fence)
|
|||
sync_status_str(status));
|
||||
|
||||
if (status <= 0) {
|
||||
struct timespec64 ts64 = ktime_to_timespec64(pt->base.timestamp);
|
||||
struct timespec64 ts64 =
|
||||
ktime_to_timespec64(pt->base.timestamp);
|
||||
|
||||
seq_printf(s, "@%lld.%09ld", (s64)ts64.tv_sec, ts64.tv_nsec);
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/* drivers/staging/android/uapi/android_alarm.h
|
||||
*
|
||||
* Copyright (C) 2006-2007 Google, Inc.
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_LINUX_ANDROID_ALARM_H
|
||||
#define _UAPI_LINUX_ANDROID_ALARM_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
enum android_alarm_type {
|
||||
/* return code bit numbers or set alarm arg */
|
||||
ANDROID_ALARM_RTC_WAKEUP,
|
||||
ANDROID_ALARM_RTC,
|
||||
ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
|
||||
ANDROID_ALARM_ELAPSED_REALTIME,
|
||||
ANDROID_ALARM_SYSTEMTIME,
|
||||
|
||||
ANDROID_ALARM_TYPE_COUNT,
|
||||
|
||||
/* return code bit numbers */
|
||||
/* ANDROID_ALARM_TIME_CHANGE = 16 */
|
||||
};
|
||||
|
||||
enum android_alarm_return_flags {
|
||||
ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP,
|
||||
ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC,
|
||||
ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK =
|
||||
1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
|
||||
ANDROID_ALARM_ELAPSED_REALTIME_MASK =
|
||||
1U << ANDROID_ALARM_ELAPSED_REALTIME,
|
||||
ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME,
|
||||
ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16
|
||||
};
|
||||
|
||||
/* Disable alarm */
|
||||
#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4))
|
||||
|
||||
/* Ack last alarm and wait for next */
|
||||
#define ANDROID_ALARM_WAIT _IO('a', 1)
|
||||
|
||||
#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size)
|
||||
/* Set alarm */
|
||||
#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec)
|
||||
#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec)
|
||||
#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec)
|
||||
#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec)
|
||||
#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0)))
|
||||
#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4)
|
||||
|
||||
#endif
|
|
@ -11,8 +11,7 @@ static bool find_by_address(u64 base_address)
|
|||
struct resource res;
|
||||
|
||||
while (dn) {
|
||||
if (of_can_translate_address(dn)
|
||||
&& !of_address_to_resource(dn, 0, &res)) {
|
||||
if (!of_address_to_resource(dn, 0, &res)) {
|
||||
if (res.start == base_address) {
|
||||
of_node_put(dn);
|
||||
return true;
|
||||
|
|
|
@ -91,8 +91,10 @@ static int clk_wzrd_clk_notifier(struct notifier_block *nb, unsigned long event,
|
|||
|
||||
if (ndata->clk == clk_wzrd->clk_in1)
|
||||
max = clk_wzrd_max_freq[clk_wzrd->speed_grade - 1];
|
||||
if (ndata->clk == clk_wzrd->axi_clk)
|
||||
else if (ndata->clk == clk_wzrd->axi_clk)
|
||||
max = WZRD_ACLK_MAX_FREQ;
|
||||
else
|
||||
return NOTIFY_DONE; /* should never happen */
|
||||
|
||||
switch (event) {
|
||||
case PRE_RATE_CHANGE:
|
||||
|
@ -239,6 +241,7 @@ static int clk_wzrd_probe(struct platform_device *pdev)
|
|||
/* register div per output */
|
||||
for (i = WZRD_NUM_OUTPUTS - 1; i >= 0 ; i--) {
|
||||
const char *clkout_name;
|
||||
|
||||
if (of_property_read_string_index(np, "clock-output-names", i,
|
||||
&clkout_name)) {
|
||||
dev_err(&pdev->dev,
|
||||
|
|
|
@ -168,7 +168,7 @@ config COMEDI_PCL730
|
|||
|
||||
config COMEDI_PCL812
|
||||
tristate "Advantech PCL-812/813 and ADlink ACL-8112/8113/8113/8216"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for Advantech PCL-812/PG, PCL-813/B, ADLink
|
||||
ACL-8112DG/HG/PG, ACL-8113, ACL-8216, ICP DAS A-821PGH/PGL/PGL-NDA,
|
||||
|
@ -179,7 +179,7 @@ config COMEDI_PCL812
|
|||
|
||||
config COMEDI_PCL816
|
||||
tristate "Advantech PCL-814 and PCL-816 ISA card support"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for Advantech PCL-814 and PCL-816 ISA cards
|
||||
|
||||
|
@ -188,7 +188,7 @@ config COMEDI_PCL816
|
|||
|
||||
config COMEDI_PCL818
|
||||
tristate "Advantech PCL-718 and PCL-818 ISA card support"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for Advantech PCL-818 ISA cards
|
||||
PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818 and PCL-718
|
||||
|
@ -281,7 +281,7 @@ config COMEDI_DAS08_ISA
|
|||
|
||||
config COMEDI_DAS16
|
||||
tristate "DAS-16 compatible ISA and PC/104 card support"
|
||||
depends on ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
select COMEDI_8255
|
||||
---help---
|
||||
Enable support for Keithley Metrabyte/ComputerBoards DAS16
|
||||
|
@ -309,7 +309,7 @@ config COMEDI_DAS800
|
|||
|
||||
config COMEDI_DAS1800
|
||||
tristate "DAS1800 and compatible ISA card support"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for DAS1800 and compatible ISA cards
|
||||
Keithley Metrabyte DAS-1701ST, DAS-1701ST-DA, DAS-1701/AO,
|
||||
|
@ -372,7 +372,7 @@ config COMEDI_DT2817
|
|||
|
||||
config COMEDI_DT282X
|
||||
tristate "Data Translation DT2821 series and DT-EZ ISA card support"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for Data Translation DT2821 series including DT-EZ
|
||||
DT2821, DT2821-F-16SE, DT2821-F-8DI, DT2821-G-16SE, DT2821-G-8DI,
|
||||
|
@ -462,7 +462,7 @@ config COMEDI_ADQ12B
|
|||
|
||||
config COMEDI_NI_AT_A2150
|
||||
tristate "NI AT-A2150 ISA card support"
|
||||
depends on VIRT_TO_BUS && ISA_DMA_API
|
||||
select COMEDI_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for National Instruments AT-A2150 cards
|
||||
|
||||
|
@ -502,7 +502,7 @@ config COMEDI_NI_ATMIO16D
|
|||
config COMEDI_NI_LABPC_ISA
|
||||
tristate "NI Lab-PC and compatibles ISA support"
|
||||
select COMEDI_NI_LABPC
|
||||
select COMEDI_NI_LABPC_ISADMA if ISA_DMA_API && VIRT_TO_BUS
|
||||
select COMEDI_NI_LABPC_ISADMA if ISA_DMA_API
|
||||
---help---
|
||||
Enable support for National Instruments Lab-PC and compatibles
|
||||
Lab-PC-1200, Lab-PC-1200AI, Lab-PC+.
|
||||
|
@ -724,7 +724,6 @@ config COMEDI_ADL_PCI9111
|
|||
config COMEDI_ADL_PCI9118
|
||||
tristate "ADLink PCI-9118DG, PCI-9118HG, PCI-9118HR support"
|
||||
depends on HAS_DMA
|
||||
depends on VIRT_TO_BUS
|
||||
---help---
|
||||
Enable support for ADlink PCI-9118DG, PCI-9118HG, PCI-9118HR cards
|
||||
|
||||
|
@ -1263,12 +1262,16 @@ config COMEDI_DAS08
|
|||
tristate
|
||||
select COMEDI_8255
|
||||
|
||||
config COMEDI_ISADMA
|
||||
tristate
|
||||
|
||||
config COMEDI_NI_LABPC
|
||||
tristate
|
||||
select COMEDI_8255
|
||||
|
||||
config COMEDI_NI_LABPC_ISADMA
|
||||
tristate
|
||||
select COMEDI_ISADMA
|
||||
|
||||
config COMEDI_NI_TIO
|
||||
tristate
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
/*
|
||||
comedi/comedi_compat32.c
|
||||
32-bit ioctl compatibility for 64-bit comedi kernel module.
|
||||
|
||||
Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
|
||||
Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
* comedi/comedi_compat32.c
|
||||
* 32-bit ioctl compatibility for 64-bit comedi kernel module.
|
||||
*
|
||||
* Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
|
||||
* Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
|
||||
*
|
||||
* COMEDI - Linux Control and Measurement Device Interface
|
||||
* Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/compat.h>
|
||||
|
@ -27,11 +27,15 @@
|
|||
|
||||
#define COMEDI32_CHANINFO _IOR(CIO, 3, struct comedi32_chaninfo_struct)
|
||||
#define COMEDI32_RANGEINFO _IOR(CIO, 8, struct comedi32_rangeinfo_struct)
|
||||
/* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR.
|
||||
* It's too late to change it now, but it only affects the command number. */
|
||||
/*
|
||||
* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR.
|
||||
* It's too late to change it now, but it only affects the command number.
|
||||
*/
|
||||
#define COMEDI32_CMD _IOR(CIO, 9, struct comedi32_cmd_struct)
|
||||
/* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR.
|
||||
* It's too late to change it now, but it only affects the command number. */
|
||||
/*
|
||||
* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR.
|
||||
* It's too late to change it now, but it only affects the command number.
|
||||
*/
|
||||
#define COMEDI32_CMDTEST _IOR(CIO, 10, struct comedi32_cmd_struct)
|
||||
#define COMEDI32_INSNLIST _IOR(CIO, 11, struct comedi32_insnlist_struct)
|
||||
#define COMEDI32_INSN _IOR(CIO, 12, struct comedi32_insn_struct)
|
||||
|
@ -39,7 +43,7 @@
|
|||
struct comedi32_chaninfo_struct {
|
||||
unsigned int subdev;
|
||||
compat_uptr_t maxdata_list; /* 32-bit 'unsigned int *' */
|
||||
compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */
|
||||
compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */
|
||||
compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */
|
||||
unsigned int unused[4];
|
||||
};
|
||||
|
@ -62,16 +66,16 @@ struct comedi32_cmd_struct {
|
|||
unsigned int scan_end_arg;
|
||||
unsigned int stop_src;
|
||||
unsigned int stop_arg;
|
||||
compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */
|
||||
compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */
|
||||
unsigned int chanlist_len;
|
||||
compat_uptr_t data; /* 32-bit 'short *' */
|
||||
compat_uptr_t data; /* 32-bit 'short *' */
|
||||
unsigned int data_len;
|
||||
};
|
||||
|
||||
struct comedi32_insn_struct {
|
||||
unsigned int insn;
|
||||
unsigned int n;
|
||||
compat_uptr_t data; /* 32-bit 'unsigned int *' */
|
||||
compat_uptr_t data; /* 32-bit 'unsigned int *' */
|
||||
unsigned int subdev;
|
||||
unsigned int chanspec;
|
||||
unsigned int unused[3];
|
||||
|
@ -79,7 +83,7 @@ struct comedi32_insn_struct {
|
|||
|
||||
struct comedi32_insnlist_struct {
|
||||
unsigned int n_insns;
|
||||
compat_uptr_t insns; /* 32-bit 'struct comedi_insn *' */
|
||||
compat_uptr_t insns; /* 32-bit 'struct comedi_insn *' */
|
||||
};
|
||||
|
||||
/* Handle translated ioctl. */
|
||||
|
@ -215,10 +219,12 @@ static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32,
|
|||
int err;
|
||||
unsigned int temp;
|
||||
|
||||
/* Copy back most of cmd structure. */
|
||||
/* Assume the pointer values are already valid. */
|
||||
/* (Could use ptr_to_compat() to set them, but that wasn't implemented
|
||||
* until kernel version 2.6.11.) */
|
||||
/*
|
||||
* Copy back most of cmd structure.
|
||||
*
|
||||
* Assume the pointer values are already valid.
|
||||
* (Could use ptr_to_compat() to set them.)
|
||||
*/
|
||||
if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd)) ||
|
||||
!access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32)))
|
||||
return -EFAULT;
|
||||
|
@ -262,7 +268,7 @@ static int compat_cmd(struct file *file, unsigned long arg)
|
|||
{
|
||||
struct comedi_cmd __user *cmd;
|
||||
struct comedi32_cmd_struct __user *cmd32;
|
||||
int rc;
|
||||
int rc, err;
|
||||
|
||||
cmd32 = compat_ptr(arg);
|
||||
cmd = compat_alloc_user_space(sizeof(*cmd));
|
||||
|
@ -271,7 +277,15 @@ static int compat_cmd(struct file *file, unsigned long arg)
|
|||
if (rc)
|
||||
return rc;
|
||||
|
||||
return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
|
||||
rc = translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd);
|
||||
if (rc == -EAGAIN) {
|
||||
/* Special case: copy cmd back to user. */
|
||||
err = put_compat_cmd(cmd32, cmd);
|
||||
if (err)
|
||||
rc = err;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Handle 32-bit COMEDI_CMDTEST ioctl. */
|
||||
|
@ -395,10 +409,12 @@ static int compat_insn(struct file *file, unsigned long arg)
|
|||
return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn);
|
||||
}
|
||||
|
||||
/* Process untranslated ioctl. */
|
||||
/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
|
||||
static inline int raw_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
/*
|
||||
* compat_ioctl file operation.
|
||||
*
|
||||
* Returns -ENOIOCTLCMD for unrecognised ioctl codes.
|
||||
*/
|
||||
long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
|
@ -445,10 +461,3 @@ static inline int raw_ioctl(struct file *file, unsigned int cmd,
|
|||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* compat_ioctl file operation. */
|
||||
/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */
|
||||
long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
return raw_ioctl(file, cmd, arg);
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
/*
|
||||
comedi/comedi_compat32.h
|
||||
32-bit ioctl compatibility for 64-bit comedi kernel module.
|
||||
|
||||
Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
|
||||
Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
* comedi/comedi_compat32.h
|
||||
* 32-bit ioctl compatibility for 64-bit comedi kernel module.
|
||||
*
|
||||
* Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk>
|
||||
* Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/>
|
||||
*
|
||||
* COMEDI - Linux Control and Measurement Device Interface
|
||||
* Copyright (C) 1997-2007 David A. Schleef <ds@schleef.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _COMEDI_COMPAT32_H
|
||||
#define _COMEDI_COMPAT32_H
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue