aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/time/itimer.c
blob: 02068b2d5862ce5b8a2c023b1c314f04e2320561 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 1992 Darren Senn
 */

/* These are all the functions necessary to implement itimers */

#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/syscalls.h>
#include <linux/time.h>
#include <linux/sched/signal.h>
#include <linux/sched/cputime.h>
#include <linux/posix-timers.h>
#include <linux/hrtimer.h>
#include <trace/events/timer.h>
#include <linux/compat.h>

#include <linux/uaccess.h>

/**
 * itimer_get_remtime - get remaining time for the timer
 *
 * @timer: the timer to read
 *
 * Returns the delta between the expiry time and now, which can be
 * less than zero or 1usec for an pending expired timer
 */
static struct timeval itimer_get_remtime(struct hrtimer *timer)
{
	ktime_t rem = __hrtimer_get_remaining(timer, true);

	/*
	 * Racy but safe: if the itimer expires after the above
	 * hrtimer_get_remtime() call but before this condition
	 * then we return 0 - which is correct.
	 */
	if (hrtimer_active(timer)) {
		if (rem <= 0)
			rem = NSEC_PER_USEC;
	} else
		rem = 0;

	return ktime_to_timeval(rem);
}

static void get_cpu_itimer(struct task_struct *tsk, unsigned int clock_id,
			   struct itimerval *const value)
{
	u64 val, interval;
	struct cpu_itimer *it = &tsk->signal->it[clock_id];

	spin_lock_irq(&tsk->sighand->siglock);

	val = it->expires;
	interval = it->incr;
	if (val) {
		struct task_cputime cputime;
		u64 t;

		thread_group_cputimer(tsk, &cputime);
		if (clock_id == CPUCLOCK_PROF)
			t = cputime.utime + cputime.stime;
		else
			/* CPUCLOCK_VIRT */
			t = cputime.utime;

		if (val < t)
			/* about to fire */
			val = TICK_NSEC;
		else
			val -= t;
	}

	spin_unlock_irq(&tsk->sighand->siglock);

	value->it_value = ns_to_timeval(val);
	value->it_interval = ns_to_timeval(interval);
}

int do_getitimer(int which, struct itimerval *value)
{
	struct task_struct *tsk = current;

	switch (which) {
	case ITIMER_REAL:
		spin_lock_irq(&tsk->sighand->siglock);
		value->it_value = itimer_get_remtime(&tsk->signal->real_timer);
		value->it_interval =
			ktime_to_timeval(tsk->signal->it_real_incr);
		spin_unlock_irq(&tsk->sighand->siglock);
		break;
	case ITIMER_VIRTUAL:
		get_cpu_itimer(tsk, CPUCLOCK_VIRT, value);
		break;
	case ITIMER_PROF:
		get_cpu_itimer(tsk, CPUCLOCK_PROF, value);
		break;
	default:
		return(-EINVAL);
	}
	return 0;
}

SYSCALL_DEFINE2(getitimer, int, which, struct itimerval __user *, value)
{
	int error = -EFAULT;
	struct itimerval get_buffer;

	if (value) {
		error = do_getitimer(which, &get_buffer);
		if (!error &&
		    copy_to_user(value, &get_buffer, sizeof(get_buffer)))
			error = -EFAULT;
	}
	return error;
}

#ifdef CONFIG_COMPAT
COMPAT_SYSCALL_DEFINE2(getitimer, int, which,
		       struct compat_itimerval __user *, it)
{
	struct itimerval kit;
	int error = do_getitimer(which, &kit);

	if (!error && put_compat_itimerval(it, &kit))
		error = -EFAULT;
	return error;
}
#endif


/*
 * The timer is automagically restarted, when interval != 0
 */
enum hrtimer_restart it_real_fn(struct hrtimer *timer)
{
	struct signal_struct *sig =
		container_of(timer, struct signal_struct, real_timer);
	struct pid *leader_pid = sig->pids[PIDTYPE_TGID];

	trace_itimer_expire(ITIMER_REAL, leader_pid, 0);
	kill_pid_info(SIGALRM, SEND_SIG_PRIV, leader_pid);

	return HRTIMER_NORESTART;
}

static void set_cpu_itimer(struct task_struct *tsk, unsigned int clock_id,
			   const struct itimerval *const value,
			   struct itimerval *const ovalue)
{
	u64 oval, nval, ointerval, ninterval;
	struct cpu_itimer *it = &tsk->signal->it[clock_id];

	/*
	 * Use the to_ktime conversion because that clamps the maximum
	 * value to KTIME_MAX and avoid multiplication overflows.
	 */
	nval = ktime_to_ns(timeval_to_ktime(value->it_value));
	ninterval = ktime_to_ns(timeval_to_ktime(value->it_interval));

	spin_lock_irq(&tsk->sighand->siglock);

	oval = it->expires;
	ointerval = it->incr;
	if (oval || nval) {
		if (nval > 0)
			nval += TICK_NSEC;
		set_process_cpu_timer(tsk, clock_id, &nval, &oval);
	}
	it->expires = nval;
	it->incr = ninterval;
	trace_itimer_state(clock_id == CPUCLOCK_VIRT ?
			   ITIMER_VIRTUAL : ITIMER_PROF, value, nval);

	spin_unlock_irq(&tsk->sighand->siglock);

	if (ovalue) {
		ovalue->it_value = ns_to_timeval(oval);
		ovalue->it_interval = ns_to_timeval(ointerval);
	}
}

/*
 * Returns true if the timeval is in canonical form
 */
#define timeval_valid(t) \
	(((t)->tv_sec >= 0) && (((unsigned long) (t)->tv_usec) < USEC_PER_SEC))

int do_setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
{
	struct task_struct *tsk = current;
	struct hrtimer *timer;
	ktime_t expires;

	/*
	 * Validate the timevals in value.
	 */
	if (!timeval_valid(&value->it_value) ||
	    !timeval_valid(&value->it_interval))
		return -EINVAL;

	switch (which) {
	case ITIMER_REAL:
again:
		spin_lock_irq(&tsk->sighand->siglock);
		timer = &tsk->signal->real_timer;
		if (ovalue) {
			ovalue->it_value = itimer_get_remtime(timer);
			ovalue->it_interval
				= ktime_to_timeval(tsk->signal->it_real_incr);
		}
		/* We are sharing ->siglock with it_real_fn() */
		if (hrtimer_try_to_cancel(timer) < 0) {
			spin_unlock_irq(&tsk->sighand->siglock);
			goto again;
		}
		expires = timeval_to_ktime(value->it_value);
		if (expires != 0) {
			tsk->signal->it_real_incr =
				timeval_to_ktime(value->it_interval);
			hrtimer_start(timer, expires, HRTIMER_MODE_REL);
		} else
			tsk->signal->it_real_incr = 0;

		trace_itimer_state(ITIMER_REAL, value, 0);
		spin_unlock_irq(&tsk->sighand->siglock);
		break;
	case ITIMER_VIRTUAL:
		set_cpu_itimer(tsk, CPUCLOCK_VIRT, value, ovalue);
		break;
	case ITIMER_PROF:
		set_cpu_itimer(tsk, CPUCLOCK_PROF, value, ovalue);
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

#ifdef __ARCH_WANT_SYS_ALARM

/**
 * alarm_setitimer - set alarm in seconds
 *
 * @seconds:	number of seconds until alarm
 *		0 disables the alarm
 *
 * Returns the remaining time in seconds of a pending timer or 0 when
 * the timer is not active.
 *
 * On 32 bit machines the seconds value is limited to (INT_MAX/2) to avoid
 * negative timeval settings which would cause immediate expiry.
 */
static unsigned int alarm_setitimer(unsigned int seconds)
{
	struct itimerval it_new, it_old;

#if BITS_PER_LONG < 64
	if (seconds > INT_MAX)
		seconds = INT_MAX;
#endif
	it_new.it_value.tv_sec = seconds;
	it_new.it_value.tv_usec = 0;
	it_new.it_interval.tv_sec = it_new.it_interval.tv_usec = 0;

	do_setitimer(ITIMER_REAL, &it_new, &it_old);

	/*
	 * We can't return 0 if we have an alarm pending ...  And we'd
	 * better return too much than too little anyway
	 */
	if ((!it_old.it_value.tv_sec && it_old.it_value.tv_usec) ||
	      it_old.it_value.tv_usec >= 500000)
		it_old.it_value.tv_sec++;

	return it_old.it_value.tv_sec;
}

/*
 * For backwards compatibility?  This can be done in libc so Alpha
 * and all newer ports shouldn't need it.
 */
SYSCALL_DEFINE1(alarm, unsigned int, seconds)
{
	return alarm_setitimer(seconds);
}

#endif

SYSCALL_DEFINE3(setitimer, int, which, struct itimerval __user *, value,
		struct itimerval __user *, ovalue)
{
	struct itimerval set_buffer, get_buffer;
	int error;

	if (value) {
		if(copy_from_user(&set_buffer, value, sizeof(set_buffer)))
			return -EFAULT;
	} else {
		memset(&set_buffer, 0, sizeof(set_buffer));
		printk_once(KERN_WARNING "%s calls setitimer() with new_value NULL pointer."
			    " Misfeature support will be removed\n",
			    current->comm);
	}

	error = do_setitimer(which, &set_buffer, ovalue ? &get_buffer : NULL);
	if (error || !ovalue)
		return error;

	if (copy_to_user(ovalue, &get_buffer, sizeof(get_buffer)))
		return -EFAULT;
	return 0;
}

#ifdef CONFIG_COMPAT
COMPAT_SYSCALL_DEFINE3(setitimer, int, which,
		       struct compat_itimerval __user *, in,
		       struct compat_itimerval __user *, out)
{
	struct itimerval kin, kout;
	int error;

	if (in) {
		if (get_compat_itimerval(&kin, in))
			return -EFAULT;
	} else {
		memset(&kin, 0, sizeof(kin));
	}

	error = do_setitimer(which, &kin, out ? &kout : NULL);
	if (error || !out)
		return error;
	if (put_compat_itimerval(out, &kout))
		return -EFAULT;
	return 0;
}
#endif
opt">, nl->timestamp_report); t += snprintf(buf + t, PAGE_SIZE - t, "lux=%u\n", nl->lux); t += snprintf(buf + t, PAGE_SIZE - t, "lux_max=%u\n", nl->lux_max); t += snprintf(buf + t, PAGE_SIZE - t, "hw=%u\n", nl->hw); t += snprintf(buf + t, PAGE_SIZE - t, "hw_mask=%x\n", nl->hw_mask); t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_lo=%u\n", nl->hw_thresh_lo); t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_hi=%u\n", nl->hw_thresh_hi); t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_lo=%x\n", nl->hw_limit_lo); t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_hi=%x\n", nl->hw_limit_hi); t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_lo=%x\n", nl->thresh_valid_lo); t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_hi=%x\n", nl->thresh_valid_hi); t += snprintf(buf + t, PAGE_SIZE - t, "thresholds_valid=%x\n", nl->thresholds_valid); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_change=%x\n", nl->nld_i_change); t += snprintf(buf + t, PAGE_SIZE - t, "calibration_en=%x\n", nl->calibration_en); t += snprintf(buf + t, PAGE_SIZE - t, "poll_delay_ms=%u\n", nl->poll_delay_ms); t += snprintf(buf + t, PAGE_SIZE - t, "delay_us=%u\n", nl->delay_us); t += snprintf(buf + t, PAGE_SIZE - t, "report=%u\n", nl->report); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i=%u\n", nl->nld_i); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_lo=%u\n", nl->nld_i_lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_hi=%u\n", nl->nld_i_hi); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl_n=%u\n", nl->nld_tbl_n); if (nl->nld_tbl) { if (nl->nld_tbl_n) { i = 0; n = nl->nld_tbl_n; } else { i = nl->nld_i_lo; n = nl->nld_i_hi + 1; } for (; i < n; i++) { if (nl->nld_thr) { t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].lo=%u\n", i, nl->nld_thr[i].lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].hi=%u\n", i, nl->nld_thr[i].hi); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].i_lo=%u\n", i, nl->nld_thr[i].i_lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].i_hi=%u\n", i, nl->nld_thr[i].i_hi); } if (nl->cfg->float_significance) { t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].resolution=%d.%09u\n", i, nl->nld_tbl[i].resolution.ival, nl->nld_tbl[i].resolution.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].max_range=%d.%09u\n", i, nl->nld_tbl[i].max_range.ival, nl->nld_tbl[i].max_range.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].milliamp=%d.%09u\n", i, nl->nld_tbl[i].milliamp.ival, nl->nld_tbl[i].milliamp.fval); } else { t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].resolution=%d.%06u\n", i, nl->nld_tbl[i].resolution.ival, nl->nld_tbl[i].resolution.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].max_range=%d.%06u\n", i, nl->nld_tbl[i].max_range.ival, nl->nld_tbl[i].max_range.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].milliamp=%d.%06u\n", i, nl->nld_tbl[i].milliamp.ival, nl->nld_tbl[i].milliamp.fval); } t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].delay_min_ms=%u\n", i, nl->nld_tbl[i].delay_min_ms); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].driver_data=%u\n", i, nl->nld_tbl[i].driver_data); } } return t; } EXPORT_SYMBOL_GPL(nvs_light_dbg); static u32 nvs_light_interpolate(int x1, s64 x2, int x3, int y1, int y3) { s64 dividend; s64 divisor; /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */ divisor = (x3 - x1); if (!divisor) return (u32)x2; dividend = (x2 - x1) * (y3 - y1); if (dividend < 0) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) dividend = abs64(dividend); #else dividend = abs(dividend); #endif do_div(dividend, divisor); dividend = 0 - dividend; } else { do_div(dividend, divisor); } dividend += y1; if (dividend < 0) dividend = 0; return (u32)dividend; } static int nvs_light_nld(struct nvs_light *nl, unsigned int nld_i) { nl->nld_i = nld_i; nl->nld_i_change = true; nl->cfg->resolution.ival = nl->nld_tbl[nld_i].resolution.ival; nl->cfg->resolution.fval = nl->nld_tbl[nld_i].resolution.fval; nl->cfg->max_range.ival = nl->nld_tbl[nld_i].max_range.ival; nl->cfg->max_range.fval = nl->nld_tbl[nld_i].max_range.fval; nl->cfg->milliamp.ival = nl->nld_tbl[nld_i].milliamp.ival; nl->cfg->milliamp.fval = nl->nld_tbl[nld_i].milliamp.fval; nl->cfg->delay_us_min = nl->nld_tbl[nld_i].delay_min_ms * 1000; return RET_POLL_NEXT; } /** * nvs_light_read - called after HW is read and placed in nl. * @nl: the common structure between driver and common module. * * This will handle the conversion of HW to lux value, * reporting, calculation of thresholds and poll time. * * Returns: -1 = Error and/or polling is required for next * sample regardless of being interrupt driven. * 0 = Do nothing. Lux has not changed for reporting * and same threshold values if interrupt driven. * If not interrupt driven use poll_delay_ms. * 1 = New HW thresholds are needed. * If not interrupt driven use poll_delay_ms. */ int nvs_light_read(struct nvs_light *nl) { u64 calc_i; u64 calc_f; s64 calc; s64 timestamp_diff; s64 delay; bool report_delay_min = true; unsigned int poll_delay = 0; unsigned int thr_lo; unsigned int thr_hi; int ret; if (nl->calibration_en) /* always report without report_delay_min */ nl->report = nl->cfg->report_n; if (nl->report < nl->cfg->report_n) { /* always report first sample */ /* calculate elapsed time for allowed report rate */ timestamp_diff = nl->timestamp - nl->timestamp_report; delay = (s64)nl->delay_us * 1000; if (timestamp_diff < delay) { /* data changes are happening faster than allowed to * report so we poll for the next data at an allowed * rate with interrupts disabled. */ delay -= timestamp_diff; do_div(delay, 1000); /* ns => us */ poll_delay = delay; report_delay_min = false; } } /* threshold flags */ if (nl->nld_thr) { thr_lo = nl->nld_thr[nl->nld_i].lo; thr_hi = nl->nld_thr[nl->nld_i].hi; } else { thr_lo = nl->cfg->thresh_lo; thr_hi = nl->cfg->thresh_hi; } if (thr_lo < nl->hw_mask) { nl->thresh_valid_lo = true; } else { nl->thresh_valid_lo = false; thr_lo = 0; } if (thr_hi < nl->hw_mask) { nl->thresh_valid_hi = true; } else { nl->thresh_valid_hi = false; thr_hi = 0; } if (nl->thresh_valid_lo && nl->thresh_valid_hi) nl->thresholds_valid = true; else nl->thresholds_valid = false; /* limit flags */ if (nl->nld_thr) { /* using absolute values */ if (nl->hw < nl->nld_thr[nl->nld_i].i_lo) nl->hw_limit_lo = true; else nl->hw_limit_lo = false; if (nl->hw > nl->nld_thr[nl->nld_i].i_hi) nl->hw_limit_hi = true; else nl->hw_limit_hi = false; } else { if (nl->hw < thr_lo) nl->hw_limit_lo = true; else nl->hw_limit_lo = false; if (nl->hw > (nl->hw_mask - thr_hi)) nl->hw_limit_hi = true; else nl->hw_limit_hi = false; } if (nl->hw == 0) nl->hw_limit_lo = true; if (nl->hw >= nl->hw_mask) nl->hw_limit_hi = true; /* reporting and thresholds */ if (nl->nld_i_change) { /* HW resolution just changed. Need thresholds and reporting * based on new settings. Reporting may not be this cycle due * to report_delay_min. */ nl->report = nl->cfg->report_n; } else { if (nl->thresholds_valid) { if (nl->hw < nl->hw_thresh_lo) nl->report = nl->cfg->report_n; else if (nl->hw > nl->hw_thresh_hi) nl->report = nl->cfg->report_n; } else { /* report everything if no thresholds */ nl->report = nl->cfg->report_n; } } ret = RET_NO_CHANGE; /* lux reporting */ if (nl->report && report_delay_min) { nl->report--; nl->timestamp_report = nl->timestamp; calc_f = 0; if (nl->cfg->scale.fval && !nl->dynamic_resolution_dis) { /* The mechanism below allows floating point to be * calculated here in the kernel by shifting up to * integer the floating point significant amount. * The nl->cfg->scale.fval must be a 10 base value, * e.g. 0.1, 0.01, ... 0.000001, etc. * The significance is calculated as: * s = (NVS_FLOAT_SIGNIFICANCE_* / scale.fval) so that * lux = HW * resolution * s * The NVS HAL will then convert the value to float * by multiplying the data with scale. */ if (nl->cfg->resolution.fval) { calc_f = (u64)nl->hw * nl->cfg->resolution.fval; do_div(calc_f, nl->cfg->scale.fval); } if (nl->cfg->resolution.ival) { if (nl->cfg->float_significance) calc_i = NVS_FLOAT_SIGNIFICANCE_NANO; else calc_i = NVS_FLOAT_SIGNIFICANCE_MICRO; do_div(calc_i, nl->cfg->scale.fval); calc_i *= (u64)nl->hw * nl->cfg->resolution.ival; } else { calc_i = 0; } } else { calc_i = nl->hw; } calc = (s64)(calc_i + calc_f); if (nl->calibration_en) /* when in calibration mode just return lux value */ nl->lux = (u32)calc; else /* get calibrated value if not in calibration mode */ nl->lux = nvs_light_interpolate(nl->cfg->uncal_lo, calc, nl->cfg->uncal_hi, nl->cfg->cal_lo, nl->cfg->cal_hi); if (nl->lux_max) { if (nl->lux > nl->lux_max) nl->lux = nl->lux_max; } /* report lux */ nl->handler(nl->nvs_st, &nl->lux, nl->timestamp_report); if (nl->thresholds_valid && !nl->report) { /* calculate low threshold */ calc = (s64)nl->hw; calc -= thr_lo; if (calc < 0) /* low threshold is disabled */ nl->hw_thresh_lo = 0; else nl->hw_thresh_lo = calc; /* calculate high threshold */ calc = nl->hw + thr_hi; if (calc > nl->hw_mask) /* high threshold is disabled */ nl->hw_thresh_hi = nl->hw_mask; else nl->hw_thresh_hi = calc; ret = RET_HW_UPDATE; } } /* dynamic resolution */ nl->nld_i_change = false; if (nl->nld_tbl) { /* if dynamic resolution is enabled */ /* adjust resolution if need to make room for thresholds */ if (nl->hw_limit_hi && nl->nld_i < nl->nld_i_hi) /* too many photons - decrease integration time */ ret = nvs_light_nld(nl, nl->nld_i + 1); else if (nl->hw_limit_lo && nl->nld_i > nl->nld_i_lo) /* not enough photons - increase integration time */ ret = nvs_light_nld(nl, nl->nld_i - 1); } /* poll time */ if (nl->nld_i_change) { nl->poll_delay_ms = nl->nld_tbl[nl->nld_i].delay_min_ms; } else { if (report_delay_min) poll_delay = nl->delay_us; if ((poll_delay < nl->cfg->delay_us_min) || nl->calibration_en) poll_delay = nl->cfg->delay_us_min; nl->poll_delay_ms = poll_delay / 1000; } if (nl->report || nl->calibration_en) ret = RET_POLL_NEXT; /* poll for next sample */ return ret; } EXPORT_SYMBOL_GPL(nvs_light_read); /** * nvs_light_enable - called when the light sensor is enabled. * @nl: the common structure between driver and common module. * * This inititializes the nl NVS variables. * * Returns 0 on success or a negative error code. */ int nvs_light_enable(struct nvs_light *nl) { if (!nl->cfg->report_n) nl->cfg->report_n = 1; nl->report = nl->cfg->report_n; nl->timestamp_report = 0; nl->hw_thresh_hi = 0; nl->hw_thresh_lo = -1; if (nl->nld_tbl) nvs_light_nld(nl, nl->nld_i_hi); nl->poll_delay_ms = nl->cfg->delay_us_min / 1000; if (nl->cfg->scale.ival == 1 && !nl->cfg->scale.fval) nl->calibration_en = true; else nl->calibration_en = false; return 0; } EXPORT_SYMBOL_GPL(nvs_light_enable); /** * nvs_light_of_dt - called during system boot to acquire * dynamic resolution table index limits. * @nl: the common structure between driver and common module. * @np: device node pointer. * @dev_name: device name string. Typically a string to "light" * or NULL. * * Returns 0 on success or a negative error code. * * Driver must initialize variables if no success. * NOTE: DT must have both indexes for a success. */ int nvs_light_of_dt(struct nvs_light *nl, const struct device_node *np, const char *dev_name) { bool nld_thr_disable; char str[256]; unsigned int i; int ret; int ret_t; if (!nl->cfg) return -EINVAL; nl->cfg->flags |= SENSOR_FLAG_ON_CHANGE_MODE; if (np == NULL) return -EINVAL; if (dev_name == NULL) dev_name = NVS_LIGHT_STRING; if (nl->nld_thr) { /* nl->nld_tbl_n == 0 is allowed in case HW driver provides * hardcoded values in nl->nld_thr */ if (nl->nld_tbl_n) nld_thr_disable = true; else nld_thr_disable = false; for (i = 0; i < nl->nld_tbl_n; i++) { nl->nld_thr[i].lo = nl->cfg->thresh_lo; nl->nld_thr[i].i_lo = nl->cfg->thresh_lo; ret = snprintf(str, sizeof(str), "%s_nld_thr_lo_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].lo); if (!ret) nld_thr_disable = false; } ret = snprintf(str, sizeof(str), "%s_nld_thr_i_lo_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].i_lo); if (!ret) nld_thr_disable = false; } nl->nld_thr[i].hi = nl->cfg->thresh_hi; nl->nld_thr[i].i_hi = nl->hw_mask - nl->cfg->thresh_hi; ret = snprintf(str, sizeof(str), "%s_nld_thr_hi_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].hi); if (!ret) nld_thr_disable = false; } ret = snprintf(str, sizeof(str), "%s_nld_thr_i_hi_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].i_hi); if (!ret) nld_thr_disable = false; } } if (nld_thr_disable) /* there isn't a DT entry so disable this feature */ nl->nld_thr = NULL; } ret = snprintf(str, sizeof(str), "%s_lux_maximum", dev_name); if (ret > 0) of_property_read_u32(np, str, &nl->lux_max); ret_t = -EINVAL; ret = snprintf(str, sizeof(str), "%s_dynamic_resolution_index_limit_low", dev_name); if (ret > 0) ret_t = of_property_read_u32(np, str, &nl->nld_i_lo); ret = snprintf(str, sizeof(str), "%s_dynamic_resolution_index_limit_high", dev_name); if (ret > 0) ret_t |= of_property_read_u32(np, str, &nl->nld_i_hi); if (nl->nld_i_hi < nl->nld_i_lo) return -EINVAL; return ret_t; } EXPORT_SYMBOL_GPL(nvs_light_of_dt); /** * nvs_light_resolution - runtime mechanism to modify nld_i_lo. * @nl: the common structure between driver and common module. * @resolution: an integer value that will be nld_i_lo. * * Returns 0 on success or a negative error code if a dynamic * resolution table exists. Otherwise a 1 is returned. * * NOTE: A returned 1 allows the NVS layer above this one to * simply store the new resolution value. * NOTE: Caller can check nld_i_change to update the HW with the * new indexed values. */ int nvs_light_resolution(struct nvs_light *nl, int resolution) { if (nl->nld_tbl == NULL) return 1; if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } if (resolution < 0 || resolution >= nl->nld_tbl_n) { pr_err("%s ERR: nld_i_lo (%d) out of range (0-%u)\n", __func__, resolution, nl->nld_tbl_n - 1); return -EINVAL; } if (resolution > nl->nld_i_hi) { pr_err("%s ERR: nld_i_lo (%d) > nld_i_hi (%u)\n", __func__, resolution, nl->nld_i_hi); return -EINVAL; } nl->nld_i_lo = resolution; if (nl->nld_i < nl->nld_i_lo) nvs_light_nld(nl, nl->nld_i_lo); return 0; } EXPORT_SYMBOL_GPL(nvs_light_resolution); /** * nvs_light_max_range - runtime mechanism to modify nld_i_hi. * @nl: the common structure between driver and common module. * @max_range: an integer value that will be nld_i_hi. * * Returns 0 on success or a negative error code if a dynamic * resolution table exists. Otherwise a 1 is returned. * * NOTE: A returned 1 allows the NVS layer above this one to * simply store the new max_range value. * NOTE: Caller can check nld_i_change to update the HW with the * new indexed values. */ int nvs_light_max_range(struct nvs_light *nl, int max_range) { if (nl->nld_tbl == NULL) return 1; if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } if (max_range < 0 || max_range >= nl->nld_tbl_n) { pr_err("%s ERR: nld_i_hi (%d) out of range (0-%u)\n", __func__, max_range, nl->nld_tbl_n - 1); return -EINVAL; } if (max_range < nl->nld_i_lo) { pr_err("%s ERR: nld_i_hi (%d) < nld_i_lo (%u)\n", __func__, max_range, nl->nld_i_lo); return -EINVAL; } nl->nld_i_hi = max_range; if (nl->nld_i > nl->nld_i_hi) nvs_light_nld(nl, nl->nld_i_hi); return 0; } EXPORT_SYMBOL_GPL(nvs_light_max_range); /** * nvs_light_threshold_calibrate_lo - runtime mechanism to * modify calibrated/uncalibrated low value. * @nl: the common structure between driver and common module. * @lo: either cal_lo or thresh_lo. * * Returns 0 on success or a negative error code. * * NOTE: If not in calibration mode then thresholds are modified * instead. */ int nvs_light_threshold_calibrate_lo(struct nvs_light *nl, int lo) { bool i_thr; unsigned int i; if (nl->calibration_en) { nl->cfg->uncal_lo = nl->lux; nl->cfg->cal_lo = lo; } else { if (nl->nld_thr) { if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } i = lo >> NVS_LIGHT_THRESH_CMD_SHIFT; i_thr = (bool)(i & NVS_LIGHT_THRESH_CMD_SEL_MSK); i &= NVS_LIGHT_THRESH_CMD_NDX_MSK; if (i >= nl->nld_tbl_n) { pr_err("%s ERR: index %d > %u\n", __func__, i, nl->nld_tbl_n - 1); return -EINVAL; } if (i_thr) nl->nld_thr[i].i_lo = lo & nl->hw_mask; else nl->nld_thr[i].lo = lo & nl->hw_mask; } else { nl->cfg->thresh_lo = lo; } } return 0; } EXPORT_SYMBOL_GPL(nvs_light_threshold_calibrate_lo); /** * nvs_light_threshold_calibrate_hi - runtime mechanism to * modify calibrated/uncalibrated high value. * @nl: the common structure between driver and common module. * @hi: either cal_hi or thresh_hi. * * Returns 0 on success or a negative error code. * * NOTE: If not in calibration mode then thresholds are modified * instead. */ int nvs_light_threshold_calibrate_hi(struct nvs_light *nl, int hi) { bool i_thr; unsigned int i; if (nl->calibration_en) { nl->cfg->uncal_hi = nl->lux; nl->cfg->cal_hi = hi; } else { if (nl->nld_thr) { if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } i = hi >> NVS_LIGHT_THRESH_CMD_SHIFT; i_thr = (bool)(i & NVS_LIGHT_THRESH_CMD_SEL_MSK); i &= NVS_LIGHT_THRESH_CMD_NDX_MSK; if (i >= nl->nld_tbl_n) { pr_err("%s ERR: index %u > %u\n", __func__, i, nl->nld_tbl_n - 1); return -EINVAL; } if (i_thr) nl->nld_thr[i].i_hi = hi & nl->hw_mask; else nl->nld_thr[i].hi = hi & nl->hw_mask; } else { nl->cfg->thresh_hi = hi; } } return 0; } EXPORT_SYMBOL_GPL(nvs_light_threshold_calibrate_hi); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("NVidia Sensor light module"); MODULE_AUTHOR("NVIDIA Corporation");