/* * The BU27034 does not have interrupt to trigger the data read when a * measurement has finished. Hence we poll the VALID bit in a thread. We will * try to wake the thread BU27034_MEAS_WAIT_PREMATURE_MS milliseconds before * the expected sampling time to prevent the drifting. * * If we constantly wake up a bit too late we would eventually skip a sample. * And because the sleep can't wake up _exactly_ at given time this would be * inevitable even if the sensor clock would be perfectly phase-locked to CPU * clock - which we can't say is the case. * * This is still fragile. No matter how big advance do we have, we will still * risk of losing a sample because things can in a rainy-day scenario be * delayed a lot. Yet, more we reserve the time for polling, more we also lose * the performance by spending cycles polling the register. So, selecting this * value is a balancing dance between severity of wasting CPU time and severity * of losing samples. * * In most cases losing the samples is not _that_ crucial because light levels * tend to change slowly. * * Other option that was pointed to me would be always sleeping 1/2 of the * measurement time, checking the VALID bit and just sleeping again if the bit * was not set. That should be pretty tolerant against missing samples due to * the scheduling delays while also not wasting much of cycles for polling. * Downside is that the time-stamps would be very inaccurate as the wake-up * would not really be tied to the sensor toggling the valid bit. This would also * result 'jumps' in the time-stamps when the delay drifted so that wake-up was * performed during the consecutive wake-ups (Or, when sensor and CPU clocks * were very different and scheduling the wake-ups was very close to given * timeout - and when the time-outs were very close to the actual sensor * sampling, Eg. once in a blue moon, two consecutive time-outs would occur * without having a sample ready).
*/ #define BU27034_MEAS_WAIT_PREMATURE_MS 5 #define BU27034_DATA_WAIT_TIME_US 1000 #define BU27034_TOTAL_DATA_WAIT_TIME_US (BU27034_MEAS_WAIT_PREMATURE_MS * 1000)
/* * Available scales with gain 1x - 1024x, timings 55, 100, 200, 400 mS * Time impacts to gain: 1x, 2x, 4x, 8x. * * => Max total gain is HWGAIN * gain by integration time (8 * 1024) = 8192 * if 1x gain is scale 1, scale for 2x gain is 0.5, 4x => 0.25, * ... 8192x => 0.0001220703125 => 122070.3125 nanos * * Using NANO precision for scale, we must use scale 16x corresponding gain 1x * to avoid precision loss. (8x would result scale 976 562.5(nanos).
*/ #define BU27034_SCALE_1X 16
/* See the data sheet for the "Gain Setting" table */ #define BU27034_GSEL_1X 0x00 /* 00000 */ #define BU27034_GSEL_4X 0x08 /* 01000 */ #define BU27034_GSEL_32X 0x0b /* 01011 */ #define BU27034_GSEL_256X 0x18 /* 11000 */ #define BU27034_GSEL_512X 0x19 /* 11001 */ #define BU27034_GSEL_1024X 0x1a /* 11010 */
/* * Measurement modes are 55, 100, 200 and 400 mS modes - which do have direct * multiplying impact to the data register values (similar to gain). * * This means that if meas-mode is changed for example from 400 => 200, * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8.
*/ #define BU27034_MEAS_MODE_100MS 0 #define BU27034_MEAS_MODE_55MS 1 #define BU27034_MEAS_MODE_200MS 2 #define BU27034_MEAS_MODE_400MS 4
staticconststruct iio_chan_spec bu27034_channels[] = {
{
.type = IIO_LIGHT,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.channel = BU27034_CHAN_ALS,
.scan_index = BU27034_CHAN_ALS,
.scan_type = {
.sign = 'u',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_CPU,
},
}, /* * The BU27034 DATA0 and DATA1 channels are both on the visible light * area (mostly). The data0 sensitivity peaks at 500nm, DATA1 at 600nm. * These wave lengths are cyan(ish) and orange(ish), making these * sub-optiomal candidates for R/G/B standardization. Hence the * colour modifier is omitted.
*/
BU27034_CHAN_DATA(DATA0),
BU27034_CHAN_DATA(DATA1),
IIO_CHAN_SOFT_TIMESTAMP(4),
};
struct bu27034_data { struct regmap *regmap; struct device *dev; /* * Protect gain and time during scale adjustment and data reading. * Protect measurement enabling/disabling.
*/ struct mutex mutex; struct iio_gts gts; struct task_struct *task;
__le16 raw[BU27034_NUM_HW_DATA_CHANS]; struct {
u32 mlux;
__le16 channels[BU27034_NUM_HW_DATA_CHANS];
aligned_s64 ts;
} scan;
};
mutex_lock(&data->mutex);
ret = _bu27034_get_scale(data, channel, val, val2);
mutex_unlock(&data->mutex); if (ret) return ret;
return IIO_VAL_INT_PLUS_NANO;
}
/* Caller should hold the lock to protect lux reading */ staticint bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel)
{ staticconstint reg[] = {
[BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2,
[BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3,
}; int mask, val;
val = FIELD_PREP(BU27034_MASK_D01_GAIN, sel);
mask = BU27034_MASK_D01_GAIN;
/* * We try to change the time in such way that the scale is maintained for * given channels by adjusting gain so that it compensates the time change.
*/ staticint bu27034_try_set_int_time(struct bu27034_data *data, int time_us)
{ struct bu27034_gain_check gains[] = {
{ .chan = BU27034_CHAN_DATA0 },
{ .chan = BU27034_CHAN_DATA1 },
}; int numg = ARRAY_SIZE(gains); int ret, int_time_old, i;
guard(mutex)(&data->mutex);
ret = bu27034_get_int_time(data); if (ret < 0) return ret;
int_time_old = ret;
if (!iio_gts_valid_time(&data->gts, time_us)) {
dev_err(data->dev, "Unsupported integration time %u\n",
time_us); return -EINVAL;
}
if (time_us == int_time_old) return 0;
for (i = 0; i < numg; i++) {
ret = bu27034_get_gain(data, gains[i].chan, &gains[i].old_gain); if (ret) return 0;
ret = iio_gts_find_new_gain_by_old_gain_time(&data->gts,
gains[i].old_gain,
int_time_old, time_us,
&gains[i].new_gain); if (ret) { int scale1, scale2; bool ok;
_bu27034_get_scale(data, gains[i].chan, &scale1, &scale2);
dev_dbg(data->dev, "chan %u, can't support time %u with scale %u %u\n",
gains[i].chan, time_us, scale1, scale2);
if (gains[i].new_gain < 0) return ret;
/* * If caller requests for integration time change and we * can't support the scale - then the caller should be * prepared to 'pick up the pieces and deal with the * fact that the scale changed'.
*/
ret = iio_find_closest_gain_low(&data->gts,
gains[i].new_gain, &ok);
if (!ok)
dev_dbg(data->dev, "optimal gain out of range for chan %u\n",
gains[i].chan);
if (ret < 0) {
dev_dbg(data->dev, "Total gain increase. Risk of saturation");
ret = iio_gts_get_min_gain(&data->gts); if (ret < 0) return ret;
}
dev_dbg(data->dev, "chan %u scale changed\n",
gains[i].chan);
gains[i].new_gain = ret;
dev_dbg(data->dev, "chan %u new gain %u\n",
gains[i].chan, gains[i].new_gain);
}
}
for (i = 0; i < numg; i++) {
ret = bu27034_set_gain(data, gains[i].chan, gains[i].new_gain); if (ret) return ret;
}
return bu27034_set_int_time(data, time_us);
}
staticint bu27034_set_scale(struct bu27034_data *data, int chan, int val, int val2)
{ int ret, time_sel, gain_sel, i; bool found = false;
if (chan == BU27034_CHAN_ALS) { if (val == 0 && val2 == 1000000) return 0;
return -EINVAL;
}
guard(mutex)(&data->mutex);
ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel); if (ret) return ret;
ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel,
val, val2, &gain_sel); if (ret) { /* * Could not support scale with given time. Need to change time. * We still want to maintain the scale for all channels
*/ struct bu27034_gain_check gain; int new_time_sel;
/* * Populate information for the other channel which should also * maintain the scale.
*/ if (chan == BU27034_CHAN_DATA0)
gain.chan = BU27034_CHAN_DATA1; elseif (chan == BU27034_CHAN_DATA1)
gain.chan = BU27034_CHAN_DATA0;
ret = bu27034_get_gain(data, gain.chan, &gain.old_gain); if (ret) return ret;
/* * Iterate through all the times to see if we find one which * can support requested scale for requested channel, while * maintaining the scale for the other channel
*/ for (i = 0; i < data->gts.num_itime; i++) {
new_time_sel = data->gts.itime_table[i].sel;
if (new_time_sel == time_sel) continue;
/* Can we provide requested scale with this time? */
ret = iio_gts_find_gain_sel_for_scale_using_time(
&data->gts, new_time_sel, val, val2,
&gain_sel); if (ret) continue;
/* Can the other channel maintain scale? */
ret = iio_gts_find_new_gain_sel_by_old_gain_time(
&data->gts, gain.old_gain, time_sel,
new_time_sel, &gain.new_gain); if (!ret) { /* Yes - we found suitable time */
found = true; break;
}
} if (!found) {
dev_dbg(data->dev, "Can't set scale maintaining other channel\n"); return -EINVAL;
}
ret = bu27034_set_gain(data, gain.chan, gain.new_gain); if (ret) return ret;
ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1,
BU27034_MASK_MEAS_MODE, new_time_sel); if (ret) return ret;
}
struct bu27034_lx_coeff { unsignedint A; unsignedint B; unsignedint C; /* Indicate which of the coefficients above are negative */ bool is_neg[3];
};
staticinline u64 gain_mul_div_helper(u64 val, unsignedint gain, unsignedint div)
{ /* * Max gain for a channel is 4096. The max u64 (0xffffffffffffffffULL) * divided by 4096 is 0xFFFFFFFFFFFFF (GENMASK_ULL(51, 0)) (floored). * Thus, the 0xFFFFFFFFFFFFF is the largest value we can safely multiply * with the gain, no matter what gain is set. * * So, multiplication with max gain may overflow if val is greater than * 0xFFFFFFFFFFFFF (52 bits set).. * * If this is the case we divide first.
*/ if (val < GENMASK_ULL(51, 0)) {
val *= gain;
do_div(val, div);
} else {
do_div(val, div);
val *= gain;
}
/* * Here we could overflow even the 64bit value. Hence we * multiply with gain0 only after the divisions - even though * it may result loss of accuracy
*/
helper = coeff * ch1 * ch1;
tmp = helper * gain0;
helper = ch1 * ch1;
if (check_mul_overflow(helper, coeff, &helper)) return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1);
if (check_mul_overflow(helper, gain0, &tmp)) return bu27034_fixp_calc_t1_64bit(coeff, ch0, ch1, gain0, gain1);
/* First, add positive terms */ for (i = 0; i < 3; i++) if (!c->is_neg[i])
res += terms[i];
/* No positive term => zero lux */ if (!res) return 0;
/* Then, subtract negative terms (if any) */ for (i = 0; i < 3; i++) if (c->is_neg[i]) { /* * If the negative term is greater than positive - then * the darkness has taken over and we are all doomed! Eh, * I mean, then we can just return 0 lx and go out
*/ if (terms[i] >= res) return 0;
res -= terms[i];
}
meastime *= 10;
do_div(res, meastime);
return (int) res;
}
staticbool bu27034_has_valid_sample(struct bu27034_data *data)
{ int ret, val;
ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val); if (ret) {
dev_err(data->dev, "Read failed %d\n", ret);
returnfalse;
}
return val & BU27034_MASK_VALID;
}
/* * Reading the register where VALID bit is clears this bit. (So does changing * any gain / integration time configuration registers) The bit gets * set when we have acquired new data. We use this bit to indicate data * validity.
*/ staticvoid bu27034_invalidate_read_data(struct bu27034_data *data)
{
bu27034_has_valid_sample(data);
}
staticint bu27034_read_result(struct bu27034_data *data, int chan, int *res)
{ int reg[] = {
[BU27034_CHAN_DATA0] = BU27034_REG_DATA0_LO,
[BU27034_CHAN_DATA1] = BU27034_REG_DATA1_LO,
}; int valid, ret;
__le16 val;
ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
valid, (valid & BU27034_MASK_VALID),
BU27034_DATA_WAIT_TIME_US, 0); if (ret) return ret;
ret = regmap_bulk_read(data->regmap, reg[chan], &val, sizeof(val)); if (ret) return ret;
*res = le16_to_cpu(val);
return 0;
}
staticint bu27034_get_result_unlocked(struct bu27034_data *data, __le16 *res, int size)
{ int ret = 0, retry_cnt = 0;
retry: /* Get new value from sensor if data is ready */ if (bu27034_has_valid_sample(data)) {
ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
res, size); if (ret) return ret;
bu27034_invalidate_read_data(data);
} else { /* No new data in sensor. Wait and retry */
retry_cnt++;
if (retry_cnt > BU27034_RETRY_LIMIT) {
dev_err(data->dev, "No data from sensor\n");
staticint bu27034_get_single_result(struct bu27034_data *data, int chan, int *val)
{ int ret;
if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA1) return -EINVAL;
ret = bu27034_meas_set(data, true); if (ret) return ret;
ret = bu27034_get_int_time(data); if (ret < 0) return ret;
msleep(ret / 1000);
return bu27034_read_result(data, chan, val);
}
/* * The formula given by vendor for computing luxes out of data0 and data1 * (in open air) is as follows: * * Let's mark: * D0 = data0/ch0_gain/meas_time_ms * 25600 * D1 = data1/ch1_gain/meas_time_ms * 25600 * * Then: * If (D1/D0 < 1.5) * lx = (0.001193 * D0 + (-0.0000747) * D1) * ((D1 / D0 – 1.5) * 0.25 + 1) * Else * lx = (0.001193 * D0 + (-0.0000747) * D1) * * We use it here. Users who have for example some colored lens * need to modify the calculation but I hope this gives a starting point for * those working with such devices.
*/
/* * We return 0 lux if calculation fails. This should be reasonably * easy to spot from the buffers especially if raw-data channels show * valid values
*/
*val = 0;
/* Don't mess with measurement enabling while buffering */ if (!iio_device_claim_direct(idev)) return -EBUSY;
mutex_lock(&data->mutex); /* * Reading one channel at a time is inefficient but we * don't care here. Buffered version should be used if * performance is an issue.
*/
ret = result_get(data, chan->channel, val);
switch (mask) { case IIO_CHAN_INFO_SCALE: return IIO_VAL_INT_PLUS_NANO; case IIO_CHAN_INFO_INT_TIME: return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_HARDWAREGAIN:
dev_dbg(data->dev, "HARDWAREGAIN is read-only, use scale to set\n"); return -EINVAL; default: return -EINVAL;
}
}
staticint bu27034_write_raw(struct iio_dev *idev, struct iio_chan_spec const *chan, int val, int val2, long mask)
{ struct bu27034_data *data = iio_priv(idev); int ret;
if (!iio_device_claim_direct(idev)) return -EBUSY;
switch (mask) { case IIO_CHAN_INFO_SCALE:
ret = bu27034_set_scale(data, chan->channel, val, val2); break; case IIO_CHAN_INFO_INT_TIME: if (!val)
ret = bu27034_try_set_int_time(data, val2); else
ret = -EINVAL; break; default:
ret = -EINVAL; break;
}
iio_device_release_direct(idev);
return ret;
}
staticint bu27034_read_avail(struct iio_dev *idev, struct iio_chan_spec const *chan, constint **vals, int *type, int *length, long mask)
{ struct bu27034_data *data = iio_priv(idev);
staticint bu27034_chip_init(struct bu27034_data *data)
{ int ret, sel;
/* Reset */
ret = regmap_write_bits(data->regmap, BU27034_REG_SYSTEM_CONTROL,
BU27034_MASK_SW_RESET, BU27034_MASK_SW_RESET); if (ret) return dev_err_probe(data->dev, ret, "Sensor reset failed\n");
msleep(1);
ret = regmap_reinit_cache(data->regmap, &bu27034_regmap); if (ret) {
dev_err(data->dev, "Failed to reinit reg cache\n"); return ret;
}
/* * Read integration time here to ensure it is in regmap cache. We do * this to speed-up the int-time acquisition in the start of the buffer * handling thread where longer delays could make it more likely we end * up skipping a sample, and where the longer delays make timestamps * less accurate.
*/
ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); if (ret)
dev_err(data->dev, "reading integration time failed\n");
return 0;
}
staticint bu27034_wait_for_data(struct bu27034_data *data)
{ int ret, val;
ret = regmap_read_poll_timeout(data->regmap, BU27034_REG_MODE_CONTROL4,
val, val & BU27034_MASK_VALID,
BU27034_DATA_WAIT_TIME_US,
BU27034_TOTAL_DATA_WAIT_TIME_US); if (ret) {
dev_err(data->dev, "data polling %s\n",
!(val & BU27034_MASK_VALID) ? "timeout" : "fail");
return ret;
}
ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO,
&data->scan.channels[0], sizeof(data->scan.channels)); if (ret) return ret;
while (!kthread_should_stop()) { int ret;
int64_t tstamp;
msleep(wait_ms);
ret = bu27034_wait_for_data(data); if (ret) continue;
tstamp = iio_get_time_ns(idev);
if (test_bit(BU27034_CHAN_ALS, idev->active_scan_mask)) { int mlux;
ret = bu27034_calc_mlux(data, &data->scan.channels[0],
&mlux); if (ret)
dev_err(data->dev, "failed to calculate lux\n");
/* * The maximum Milli lux value we get with gain 1x time * 55mS data ch0 = 0xffff ch1 = 0xffff fits in 26 bits * so there should be no problem returning int from * computations and casting it to u32
*/
data->scan.mlux = (u32)mlux;
}
iio_push_to_buffers_with_timestamp(idev, &data->scan, tstamp);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.