diff mbox series

[RFC] drivers/hid/hid-wiimote: Improve Wiimote Balance Board input reporting

Message ID 170711555.4359978.1748472767597@mail.yahoo.com
State New
Headers show
Series [RFC] drivers/hid/hid-wiimote: Improve Wiimote Balance Board input reporting | expand

Commit Message

Sophie Dexter May 28, 2025, 10:52 p.m. UTC
Hi all,

Although the balance board is reportedly fairly accurate and not significantly affected by heavy use, https://www.sciencedirect.com/science/article/abs/pii/S0966636213003184, a few projects aiming to use Wii Balance Board as weighing scales question its accuracy for weights below 10 or 20 kilograms e.g: https://github.com/jmahmood/bbev and https://github.com/SilverLuke/PC-Fit.

The kernels' hid-wiimote module's current implementation provides 'cooked' weight sensor values in decigrams (units of 10 grams) derived by interpolating 'raw' sensor values between 0, 17 and 34kg calibration values. For sensor values higher than 34kg extrapolation continues to use the higher, 17 and 34 kg, calibration points. However, for sensor values below the 0kg calibration points the driver simply returns a fixed bottom limit of 0, and therein lies the problem.

Based on a sample of 2 balance boards, absolute sensor values from old and battered balance boards can have positive or, and crucially, negative offsets in excess of 1kg relative to their original calibrations. E.g. if a sensor has a -1.5kg offset the kernel module will report 0 for that sensor until the balance board is loaded by about 6 kg since each sensor 'sees' roughly a quarter of the total weight on the board.

A more subtle consideration is that the current implementation uses simple integer division in its interpolation calculations which on average will round
down the summed total of the four sensor values by 20 grams. 

Even more subtly, the Wii Balance Board's stiffness varies by around 1% for every 14°C temperature change, https://wiibrew.org/wiki/Wii_Balance_Board. However, the current wiimote hid driver doesn't use the balance board's temperature sensor or reference temperature calibration point.

Lastly, there is a small adjustment that Wii Fit is said to make that may be to normalise to Standard Gravity. This scales the whole result by a factor of ~0.99908.

While none of this is a problem for applications that are only concerned with relative values, e.g. games and COP (centre of pressure) applications, it is for
for me because I want reliable, absolute measurements of relatively small weights to monitor my small breed pet dogs at home.

My proposed patches addresses each of these issues
1) Report 'negative' weights
    Benefit: The balance board can be used by applications to weigh objects down to 0 kg no matter how battered they are.
    Consideration: Any weighing scales application will need a 'zeroing' facility to accommodate for any offsets from balance board calibration points.
    Risk: May break existing applications that cannot handle negative sensor values.
2) Use the Kernel's DIV_ROUND_CLOSEST macro
    Benefit: Improved accuracy.
    Consideration: Uses more CPU time, code bloat.
    Risk: none?
3a) Perform compensation for temperature and gravitational acceleration
    Benefit: Improved accuracy
    Consideration: Uses more CPU time, code bloat.
    Risk: none?
3b) Report calibration reference temperature for use by user-space applications to
    /sys/bus/hid/drivers/wiimote/<dev>/bboard_ref_temp
    Benefit: Improved accuracy (user-space applications)
    Consideration: Uses more CPU time, code bloat.
    Risk: none?

Testing: Having made these proposed changes to the kernel's hid-wiimote module, and dealing with the 'zeroing' issue in user-land, I can use my balance board to weigh both of my dogs, measurements are within 10-20 grams of those made by my veterinary practitioner's scales, and to weigh myself with measurements within 100 grams of my bathroom scales. As an independent verification I weighed 8 bottles of wine individually using kitchen scales put them on each of the 2  Wii Balance Boards available to me and got the expected result :-)

Thank you for your consideration,
Sophie.

Here follows a patch that can be applied against v6.15, commit 0ff41df (I hope I've done things correctly):

---
 drivers/hid/hid-wiimote-modules.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
 drivers/hid/hid-wiimote.h         |   1 +
 2 files changed, 130 insertions(+), 15 deletions(-)

Comments

David Rheinsberg June 10, 2025, 7:02 a.m. UTC | #1
Hi!

On Thu, May 29, 2025, at 12:52 AM, Sophie Dexter wrote:
> Hi all,
>
> Although the balance board is reportedly fairly accurate and not 
> significantly affected by heavy use, 
> https://www.sciencedirect.com/science/article/abs/pii/S0966636213003184, 
> a few projects aiming to use Wii Balance Board as weighing scales 
> question its accuracy for weights below 10 or 20 kilograms e.g: 
> https://github.com/jmahmood/bbev and 
> https://github.com/SilverLuke/PC-Fit.
>
> The kernels' hid-wiimote module's current implementation provides 
> 'cooked' weight sensor values in decigrams (units of 10 grams) derived 
> by interpolating 'raw' sensor values between 0, 17 and 34kg calibration 
> values. For sensor values higher than 34kg extrapolation continues to 
> use the higher, 17 and 34 kg, calibration points. However, for sensor 
> values below the 0kg calibration points the driver simply returns a 
> fixed bottom limit of 0, and therein lies the problem.
>
> Based on a sample of 2 balance boards, absolute sensor values from old 
> and battered balance boards can have positive or, and crucially, 
> negative offsets in excess of 1kg relative to their original 
> calibrations. E.g. if a sensor has a -1.5kg offset the kernel module 
> will report 0 for that sensor until the balance board is loaded by 
> about 6 kg since each sensor 'sees' roughly a quarter of the total 
> weight on the board.
>
> A more subtle consideration is that the current implementation uses 
> simple integer division in its interpolation calculations which on 
> average will round
> down the summed total of the four sensor values by 20 grams. 
>
> Even more subtly, the Wii Balance Board's stiffness varies by around 1% 
> for every 14°C temperature change, 
> https://wiibrew.org/wiki/Wii_Balance_Board. However, the current 
> wiimote hid driver doesn't use the balance board's temperature sensor 
> or reference temperature calibration point.
>
> Lastly, there is a small adjustment that Wii Fit is said to make that 
> may be to normalise to Standard Gravity. This scales the whole result 
> by a factor of ~0.99908.
>
> While none of this is a problem for applications that are only 
> concerned with relative values, e.g. games and COP (centre of pressure) 
> applications, it is for
> for me because I want reliable, absolute measurements of relatively 
> small weights to monitor my small breed pet dogs at home.

Thank you for the detailed explanation! This sounds all reasonable to me.

> My proposed patches addresses each of these issues
> 1) Report 'negative' weights
>     Benefit: The balance board can be used by applications to weigh 
> objects down to 0 kg no matter how battered they are.
>     Consideration: Any weighing scales application will need a 
> 'zeroing' facility to accommodate for any offsets from balance board 
> calibration points.
>     Risk: May break existing applications that cannot handle negative 
> sensor values.

This should be fine. The linux event interface can deal with those values.

> 2) Use the Kernel's DIV_ROUND_CLOSEST macro
>     Benefit: Improved accuracy.
>     Consideration: Uses more CPU time, code bloat.
>     Risk: none?

Again, sounds good to me!

> 3a) Perform compensation for temperature and gravitational acceleration
>     Benefit: Improved accuracy
>     Consideration: Uses more CPU time, code bloat.
>     Risk: none?

Sounds good!

> 3b) Report calibration reference temperature for use by user-space 
> applications to
>     /sys/bus/hid/drivers/wiimote/<dev>/bboard_ref_temp
>     Benefit: Improved accuracy (user-space applications)
>     Consideration: Uses more CPU time, code bloat.
>     Risk: none?

Doesn't the `bboard_calib` sysfs-file already include that information?

> Testing: Having made these proposed changes to the kernel's hid-wiimote 
> module, and dealing with the 'zeroing' issue in user-land, I can use my 
> balance board to weigh both of my dogs, measurements are within 10-20 
> grams of those made by my veterinary practitioner's scales, and to 
> weigh myself with measurements within 100 grams of my bathroom scales. 
> As an independent verification I weighed 8 bottles of wine individually 
> using kitchen scales put them on each of the 2  Wii Balance Boards 
> available to me and got the expected result :-)
>
> Thank you for your consideration,
> Sophie.
>
> Here follows a patch that can be applied against v6.15, commit 0ff41df 
> (I hope I've done things correctly):
>
> ---
>  drivers/hid/hid-wiimote-modules.c | 144 
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
>  drivers/hid/hid-wiimote.h         |   1 +
>  2 files changed, 130 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/hid/hid-wiimote-modules.c 
> b/drivers/hid/hid-wiimote-modules.c
> index dbccdfa63916..67bf0fed062a 100644
> --- a/drivers/hid/hid-wiimote-modules.c
> +++ b/drivers/hid/hid-wiimote-modules.c
> @@ -1302,8 +1302,30 @@ static const struct wiimod_ops wiimod_classic = {
>   * Some 3rd party devices react allergic if we try to access normal 
> Wii Remote
>   * hardware, so this extension module should be the only module that 
> is loaded
>   * on balance boards.
> - * The balance board needs 8 bytes extension data instead of basic 6 
> bytes so
> - * it needs the WIIMOD_FLAG_EXT8 flag.
> + * The balance board has 11 bytes extension data instead of basic 6 
> bytes so
> + * it needs the WIIMOD_FLAG_EXT16 flag.
> + *
> + * The stiffness of the Wii Fit's sensors decreases with temperature 
> which
> + * causes them to indicate higher than actual weight at high 
> temperature and
> + * and lower than actual weight at low temperature. The net effect is 
> about
> + * a 1% error for a difference of 14 in the balance board's 
> temperature sensor
> + * reading compared to a reference temperature. Wii Fit compensates 
> for the
> + * temperature value with the calculation: 
> + * (.999 * total_weight * -(.007 * ((board_temp - ref_temp) / 10.0) - 
> 1.0))
> + * where 0.999 is actually 0.9990813732147217 (0x3feff87980000000) and 
> .007 is
> + * actually 0.007000000216066837 (0x3f7cac0840000000, (double).007f).
> + *
> + * To do the same using integer maths we must refactor to subtract 
> fractions.
> + * For the temperature compensation part we subtract a 1429th of the 
> sensor
> + * value multiplied by the temperature difference (1/(0.007/10)), then
> + * subtract a 1089th of that value for the overall adjustment 
> (1/(1-0.99908)).
> + *
> + * The refactored equation for each sensor is:
> + * final_weight = compensated_weight - (compensated_weight / 1089)
> + * where
> + * compensated_weight = sensor_weight - ( temp_diff * sensor_weight / 
> 1429)
> + * and
> + * temp_diff = board_temp - ref_temp

Minor nit: I would prefer if pseudo-code or math is indented by a few spaces, or otherwise visually separated from the text around it.

>   */

>  static void wiimod_bboard_in_keys(struct wiimote_data *wdata, const 
> __u8 *keys)
> @@ -1316,7 +1338,7 @@ static void wiimod_bboard_in_keys(struct 
> wiimote_data *wdata, const __u8 *keys)
>  static void wiimod_bboard_in_ext(struct wiimote_data *wdata,
>                   const __u8 *ext)
>  {
> -    __s32 val[4], tmp, div;
> +    __s32 val[4], tmp, div, temp_diff, temp_corr, scale_corr;
>      unsigned int i;
>      struct wiimote_state *s = &wdata->state;

> @@ -1337,10 +1359,24 @@ static void wiimod_bboard_in_ext(struct 
> wiimote_data *wdata,
>       *    7   |  Bottom Left <15:8>      |
>       *    8   |  Bottom Left  <7:0>      |
>       *   -----+--------------------------+
> +     *    9   |  Temperature             |
> +     *   -----+--------------------------+
> +     *   10   |  NOT USED                |
> +     *   -----+--------------------------+
> +     *   11   |  Battery                 |
> +     *   -----+--------------------------+
>       *
> -     * These values represent the weight-measurements of the 
> Wii-balance
> +     * The first 8 bytes represent the weight-measurements of the 
> Wii-balance
>       * board with 16bit precision.
>       *
> +     * Temperature values less than the calibration reference 
> temperature
> +     * indicate that that sensors are stiffer and consequently produce 
> lower
> +     * readings.
> +     *
> +     * A battery level ≥ 0x82 is 4 bars, less than 0x82 and ≥ 0x7D is 
> 3 bars,
> +     * less than 0x7D and ≥ 0x78 is 2 bars, less than 0x78 and ≥ 0x6A 
> is 1
> +     * bar, and otherwise is considered empty.
> +     * 
>       * The balance-board is never reported interleaved with motionp.
>       */

> @@ -1360,23 +1396,52 @@ static void wiimod_bboard_in_ext(struct 
> wiimote_data *wdata,
>      val[3] <<= 8;
>      val[3] |= ext[7];

> +    temp_diff = ext[8] - s->calib_bboard_temp;
> +
>      /* apply calibration data */
>      for (i = 0; i < 4; i++) {
> -        if (val[i] <= s->calib_bboard[i][0]) {
> -            tmp = 0;
> -        } else if (val[i] < s->calib_bboard[i][1]) {
> +        /*
> +         * Interpolate using 0 and 17kg datum points when sensor values are
> +         * less than their 17kg calibration points.
> +         */

Can you put these comments inside of the blocks? So like this:

if (...) {
    /* comment about this block */
    ..code..
} else if (...) {
    /* another comment */
    ..code..
} else {
    /* last comment */
    ..code..
}

I think this matches better what the kernel coding-style looks like. Especially the else-block below looks different than usual.

> +        if (val[i] < s->calib_bboard[i][1]) {
>              tmp = val[i] - s->calib_bboard[i][0];
> -            tmp *= 1700;
> +            /* Divisor used for interpolation */
>              div = s->calib_bboard[i][1] - s->calib_bboard[i][0];
> -            tmp /= div ? div : 1;
> +        /*
> +         * Interpolate using 17 and 34kg datum points when sensor values are
> +         * equal to or greater than their 17kg calibration points.
> +         */
>          } else {
>              tmp = val[i] - s->calib_bboard[i][1];
> -            tmp *= 1700;
>              div = s->calib_bboard[i][2] - s->calib_bboard[i][1];
> -            tmp /= div ? div : 1;
> -            tmp += 1700;
> +            /* Add 17kg, the delta between 0 and 17kg datum points. */
> +            tmp += s->calib_bboard[i][1] - s->calib_bboard[i][0];
>          }
> -        val[i] = tmp;
> +        /*
> +         * 1,700 is the factor for interpolating between calibration points,
> +         * which are 17kg apart, to achieve 0.01kg steps. This multiplication
> +         * also helps to mitigate the effect of rounding errors introduced in
> +         * subsequent division calculations.
> +         */
> +        tmp *= 1700;
> +        /*
> +         * Apply temperature compensation of approximately -0.98% for every
> +         * 14 units of temperature, likely degrees C, above that at which
> +         * board calibration was done.
> +         */
> +        temp_corr = tmp * temp_diff;
> +        temp_corr = DIV_ROUND_CLOSEST(temp_corr, 1429);
> +        tmp -= temp_corr;
> +        /*
> +         * Nintendo's Wii Fit is said to make this correction. Maybe it is an
> +         * adjustment for the specific gravitational acceleration at their
> +         * calibration facility to normalise results to Standard Gravity.
> +         * Subtract approximately 0.092%.
> +         */        scale_corr = DIV_ROUND_CLOSEST(tmp, 1089);

This assignment should go on a newline, instead of being behind the `*/`.

> +        tmp -= scale_corr;
> +        /* finally divide by the delta between applicable calibration points*/
> +        val [i] = DIV_ROUND_CLOSEST(tmp, div ? div : 1);
>      }

>      input_report_abs(wdata->extension.input, ABS_HAT0X, val[0]);
> @@ -1464,11 +1529,43 @@ static ssize_t wiimod_bboard_calib_show(struct 
> device *dev,

>  static DEVICE_ATTR(bboard_calib, S_IRUGO, wiimod_bboard_calib_show, NULL);

> +static ssize_t wiimod_bboard_ref_temp_show(struct device *dev,
> +                    struct device_attribute *attr,
> +                    char *out)
> +{
> +    struct wiimote_data *wdata = dev_to_wii(dev);
> +    int ret;
> +    __u8 ref_temp;
> +
> +    ret = wiimote_cmd_acquire(wdata);
> +    if (ret)
> +        return ret;
> +
> +    ret = wiimote_cmd_read(wdata, 0xa40060, &ref_temp, 1);
> +    if (ret != 1) {
> +        wiimote_cmd_release(wdata);
> +        return ret < 0 ? ret : -EIO;
> +    }
> +
> +    wiimote_cmd_release(wdata);
> +
> +    spin_lock_irq(&wdata->state.lock);
> +    wdata->state.calib_bboard_temp = ref_temp;
> +    spin_unlock_irq(&wdata->state.lock);
> +
> +    ret = 0;
> +    ret += sprintf(&out[ret], "%02x\n", ref_temp);
> +
> +    return ret;
> +}
> +
> +static DEVICE_ATTR(bboard_ref_temp, S_IRUGO, 
> wiimod_bboard_ref_temp_show, NULL);
> +
>  static int wiimod_bboard_probe(const struct wiimod_ops *ops,
>                     struct wiimote_data *wdata)
>  {
>      int ret, i, j;
> -    __u8 buf[24], offs;
> +    __u8 buf[24], offs, ref_temp;

>      wiimote_cmd_acquire_noint(wdata);

> @@ -1482,6 +1579,11 @@ static int wiimod_bboard_probe(const struct 
> wiimod_ops *ops,
>          wiimote_cmd_release(wdata);
>          return ret < 0 ? ret : -EIO;
>      }
> +    ret = wiimote_cmd_read(wdata, 0xa40060, &ref_temp, 1);
> +    if (ret != 1) {
> +        wiimote_cmd_release(wdata);
> +        return ret < 0 ? ret : -EIO;
> +    }

>      wiimote_cmd_release(wdata);

> @@ -1494,6 +1596,7 @@ static int wiimod_bboard_probe(const struct 
> wiimod_ops *ops,
>              offs += 2;
>          }
>      }
> +    wdata->state.calib_bboard_temp = ref_temp;

>      wdata->extension.input = input_allocate_device();
>      if (!wdata->extension.input)
> @@ -1506,6 +1609,13 @@ static int wiimod_bboard_probe(const struct 
> wiimod_ops *ops,
>          goto err_free;
>      }

> +    ret = device_create_file(&wdata->hdev->dev,
> +                 &dev_attr_bboard_ref_temp);
> +    if (ret) {
> +        hid_err(wdata->hdev, "cannot create sysfs attribute\n");
> +        goto err_free;
> +    }
> +
>      input_set_drvdata(wdata->extension.input, wdata);
>      wdata->extension.input->open = wiimod_bboard_open;
>      wdata->extension.input->close = wiimod_bboard_close;
> @@ -1542,6 +1652,8 @@ static int wiimod_bboard_probe(const struct 
> wiimod_ops *ops,
>  err_file:
>      device_remove_file(&wdata->hdev->dev,
>                 &dev_attr_bboard_calib);
> +    device_remove_file(&wdata->hdev->dev,
> +               &dev_attr_bboard_ref_temp);
>  err_free:
>      input_free_device(wdata->extension.input);
>      wdata->extension.input = NULL;
> @@ -1558,10 +1670,12 @@ static void wiimod_bboard_remove(const struct 
> wiimod_ops *ops,
>      wdata->extension.input = NULL;
>      device_remove_file(&wdata->hdev->dev,
>                 &dev_attr_bboard_calib);
> +    device_remove_file(&wdata->hdev->dev,
> +               &dev_attr_bboard_ref_temp);
>  }

>  static const struct wiimod_ops wiimod_bboard = {
> -    .flags = WIIMOD_FLAG_EXT8,
> +    .flags = WIIMOD_FLAG_EXT16,
>      .arg = 0,
>      .probe = wiimod_bboard_probe,
>      .remove = wiimod_bboard_remove,
> diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
> index 9c12f63f6dd2..fd31797b4b06 100644
> --- a/drivers/hid/hid-wiimote.h
> +++ b/drivers/hid/hid-wiimote.h
> @@ -136,6 +136,7 @@ struct wiimote_state {

>      /* calibration/cache data */
>      __u16 calib_bboard[4][3];
> +    __u8 calib_bboard_temp;
>      __s16 calib_pro_sticks[4];
>      __u8 pressure_drums[7];
>      __u8 cache_rumble;

A bunch of minor coding-style corrections, but otherwise this looks excellent. Thanks a lot for investigating! If you resend this, please add the HID maintainers in CC. You can find this via:

$ ./scripts/get_maintainer.pl -f drivers/hid/hid-wiimote-modules.c
David Rheinsberg <david@readahead.eu> (maintainer:WIIMOTE HID DRIVER)
Jiri Kosina <jikos@kernel.org> (maintainer:HID CORE LAYER)
Benjamin Tissoires <bentiss@kernel.org> (maintainer:HID CORE LAYER)
linux-input@vger.kernel.org (open list:WIIMOTE HID DRIVER)
linux-kernel@vger.kernel.org (open list)

You can also add my reviewed-by to the patch:

Reviewed-by: David Rheinsberg <david@readahead.eu>

Thanks a lot!
David
diff mbox series

Patch

diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c
index dbccdfa63916..67bf0fed062a 100644
--- a/drivers/hid/hid-wiimote-modules.c
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -1302,8 +1302,30 @@  static const struct wiimod_ops wiimod_classic = {
  * Some 3rd party devices react allergic if we try to access normal Wii Remote
  * hardware, so this extension module should be the only module that is loaded
  * on balance boards.
- * The balance board needs 8 bytes extension data instead of basic 6 bytes so
- * it needs the WIIMOD_FLAG_EXT8 flag.
+ * The balance board has 11 bytes extension data instead of basic 6 bytes so
+ * it needs the WIIMOD_FLAG_EXT16 flag.
+ *
+ * The stiffness of the Wii Fit's sensors decreases with temperature which
+ * causes them to indicate higher than actual weight at high temperature and
+ * and lower than actual weight at low temperature. The net effect is about
+ * a 1% error for a difference of 14 in the balance board's temperature sensor
+ * reading compared to a reference temperature. Wii Fit compensates for the
+ * temperature value with the calculation: 
+ * (.999 * total_weight * -(.007 * ((board_temp - ref_temp) / 10.0) - 1.0))
+ * where 0.999 is actually 0.9990813732147217 (0x3feff87980000000) and .007 is
+ * actually 0.007000000216066837 (0x3f7cac0840000000, (double).007f).
+ *
+ * To do the same using integer maths we must refactor to subtract fractions.
+ * For the temperature compensation part we subtract a 1429th of the sensor
+ * value multiplied by the temperature difference (1/(0.007/10)), then
+ * subtract a 1089th of that value for the overall adjustment (1/(1-0.99908)).
+ *
+ * The refactored equation for each sensor is:
+ * final_weight = compensated_weight - (compensated_weight / 1089)
+ * where
+ * compensated_weight = sensor_weight - ( temp_diff * sensor_weight / 1429)
+ * and
+ * temp_diff = board_temp - ref_temp
  */
 
 static void wiimod_bboard_in_keys(struct wiimote_data *wdata, const __u8 *keys)
@@ -1316,7 +1338,7 @@  static void wiimod_bboard_in_keys(struct wiimote_data *wdata, const __u8 *keys)
 static void wiimod_bboard_in_ext(struct wiimote_data *wdata,
                  const __u8 *ext)
 {
-    __s32 val[4], tmp, div;
+    __s32 val[4], tmp, div, temp_diff, temp_corr, scale_corr;
     unsigned int i;
     struct wiimote_state *s = &wdata->state;
 
@@ -1337,10 +1359,24 @@  static void wiimod_bboard_in_ext(struct wiimote_data *wdata,
      *    7   |  Bottom Left <15:8>      |
      *    8   |  Bottom Left  <7:0>      |
      *   -----+--------------------------+
+     *    9   |  Temperature             |
+     *   -----+--------------------------+
+     *   10   |  NOT USED                |
+     *   -----+--------------------------+
+     *   11   |  Battery                 |
+     *   -----+--------------------------+
      *
-     * These values represent the weight-measurements of the Wii-balance
+     * The first 8 bytes represent the weight-measurements of the Wii-balance
      * board with 16bit precision.
      *
+     * Temperature values less than the calibration reference temperature
+     * indicate that that sensors are stiffer and consequently produce lower
+     * readings.
+     *
+     * A battery level ≥ 0x82 is 4 bars, less than 0x82 and ≥ 0x7D is 3 bars,
+     * less than 0x7D and ≥ 0x78 is 2 bars, less than 0x78 and ≥ 0x6A is 1
+     * bar, and otherwise is considered empty.
+     * 
      * The balance-board is never reported interleaved with motionp.
      */
 
@@ -1360,23 +1396,52 @@  static void wiimod_bboard_in_ext(struct wiimote_data *wdata,
     val[3] <<= 8;
     val[3] |= ext[7];
 
+    temp_diff = ext[8] - s->calib_bboard_temp;
+
     /* apply calibration data */
     for (i = 0; i < 4; i++) {
-        if (val[i] <= s->calib_bboard[i][0]) {
-            tmp = 0;
-        } else if (val[i] < s->calib_bboard[i][1]) {
+        /*
+         * Interpolate using 0 and 17kg datum points when sensor values are
+         * less than their 17kg calibration points.
+         */
+        if (val[i] < s->calib_bboard[i][1]) {
             tmp = val[i] - s->calib_bboard[i][0];
-            tmp *= 1700;
+            /* Divisor used for interpolation */
             div = s->calib_bboard[i][1] - s->calib_bboard[i][0];
-            tmp /= div ? div : 1;
+        /*
+         * Interpolate using 17 and 34kg datum points when sensor values are
+         * equal to or greater than their 17kg calibration points.
+         */
         } else {
             tmp = val[i] - s->calib_bboard[i][1];
-            tmp *= 1700;
             div = s->calib_bboard[i][2] - s->calib_bboard[i][1];
-            tmp /= div ? div : 1;
-            tmp += 1700;
+            /* Add 17kg, the delta between 0 and 17kg datum points. */
+            tmp += s->calib_bboard[i][1] - s->calib_bboard[i][0];
         }
-        val[i] = tmp;
+        /*
+         * 1,700 is the factor for interpolating between calibration points,
+         * which are 17kg apart, to achieve 0.01kg steps. This multiplication
+         * also helps to mitigate the effect of rounding errors introduced in
+         * subsequent division calculations.
+         */
+        tmp *= 1700;
+        /*
+         * Apply temperature compensation of approximately -0.98% for every
+         * 14 units of temperature, likely degrees C, above that at which
+         * board calibration was done.
+         */
+        temp_corr = tmp * temp_diff;
+        temp_corr = DIV_ROUND_CLOSEST(temp_corr, 1429);
+        tmp -= temp_corr;
+        /*
+         * Nintendo's Wii Fit is said to make this correction. Maybe it is an
+         * adjustment for the specific gravitational acceleration at their
+         * calibration facility to normalise results to Standard Gravity.
+         * Subtract approximately 0.092%.
+         */        scale_corr = DIV_ROUND_CLOSEST(tmp, 1089);
+        tmp -= scale_corr;
+        /* finally divide by the delta between applicable calibration points*/
+        val [i] = DIV_ROUND_CLOSEST(tmp, div ? div : 1);
     }
 
     input_report_abs(wdata->extension.input, ABS_HAT0X, val[0]);
@@ -1464,11 +1529,43 @@  static ssize_t wiimod_bboard_calib_show(struct device *dev,
 
 static DEVICE_ATTR(bboard_calib, S_IRUGO, wiimod_bboard_calib_show, NULL);
 
+static ssize_t wiimod_bboard_ref_temp_show(struct device *dev,
+                    struct device_attribute *attr,
+                    char *out)
+{
+    struct wiimote_data *wdata = dev_to_wii(dev);
+    int ret;
+    __u8 ref_temp;
+
+    ret = wiimote_cmd_acquire(wdata);
+    if (ret)
+        return ret;
+
+    ret = wiimote_cmd_read(wdata, 0xa40060, &ref_temp, 1);
+    if (ret != 1) {
+        wiimote_cmd_release(wdata);
+        return ret < 0 ? ret : -EIO;
+    }
+
+    wiimote_cmd_release(wdata);
+
+    spin_lock_irq(&wdata->state.lock);
+    wdata->state.calib_bboard_temp = ref_temp;
+    spin_unlock_irq(&wdata->state.lock);
+
+    ret = 0;
+    ret += sprintf(&out[ret], "%02x\n", ref_temp);
+
+    return ret;
+}
+
+static DEVICE_ATTR(bboard_ref_temp, S_IRUGO, wiimod_bboard_ref_temp_show, NULL);
+
 static int wiimod_bboard_probe(const struct wiimod_ops *ops,
                    struct wiimote_data *wdata)
 {
     int ret, i, j;
-    __u8 buf[24], offs;
+    __u8 buf[24], offs, ref_temp;
 
     wiimote_cmd_acquire_noint(wdata);
 
@@ -1482,6 +1579,11 @@  static int wiimod_bboard_probe(const struct wiimod_ops *ops,
         wiimote_cmd_release(wdata);
         return ret < 0 ? ret : -EIO;
     }
+    ret = wiimote_cmd_read(wdata, 0xa40060, &ref_temp, 1);
+    if (ret != 1) {
+        wiimote_cmd_release(wdata);
+        return ret < 0 ? ret : -EIO;
+    }
 
     wiimote_cmd_release(wdata);
 
@@ -1494,6 +1596,7 @@  static int wiimod_bboard_probe(const struct wiimod_ops *ops,
             offs += 2;
         }
     }
+    wdata->state.calib_bboard_temp = ref_temp;
 
     wdata->extension.input = input_allocate_device();
     if (!wdata->extension.input)
@@ -1506,6 +1609,13 @@  static int wiimod_bboard_probe(const struct wiimod_ops *ops,
         goto err_free;
     }
 
+    ret = device_create_file(&wdata->hdev->dev,
+                 &dev_attr_bboard_ref_temp);
+    if (ret) {
+        hid_err(wdata->hdev, "cannot create sysfs attribute\n");
+        goto err_free;
+    }
+
     input_set_drvdata(wdata->extension.input, wdata);
     wdata->extension.input->open = wiimod_bboard_open;
     wdata->extension.input->close = wiimod_bboard_close;
@@ -1542,6 +1652,8 @@  static int wiimod_bboard_probe(const struct wiimod_ops *ops,
 err_file:
     device_remove_file(&wdata->hdev->dev,
                &dev_attr_bboard_calib);
+    device_remove_file(&wdata->hdev->dev,
+               &dev_attr_bboard_ref_temp);
 err_free:
     input_free_device(wdata->extension.input);
     wdata->extension.input = NULL;
@@ -1558,10 +1670,12 @@  static void wiimod_bboard_remove(const struct wiimod_ops *ops,
     wdata->extension.input = NULL;
     device_remove_file(&wdata->hdev->dev,
                &dev_attr_bboard_calib);
+    device_remove_file(&wdata->hdev->dev,
+               &dev_attr_bboard_ref_temp);
 }
 
 static const struct wiimod_ops wiimod_bboard = {
-    .flags = WIIMOD_FLAG_EXT8,
+    .flags = WIIMOD_FLAG_EXT16,
     .arg = 0,
     .probe = wiimod_bboard_probe,
     .remove = wiimod_bboard_remove,
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
index 9c12f63f6dd2..fd31797b4b06 100644
--- a/drivers/hid/hid-wiimote.h
+++ b/drivers/hid/hid-wiimote.h
@@ -136,6 +136,7 @@  struct wiimote_state {
 
     /* calibration/cache data */
     __u16 calib_bboard[4][3];
+    __u8 calib_bboard_temp;
     __s16 calib_pro_sticks[4];
     __u8 pressure_drums[7];
     __u8 cache_rumble;