summaryrefslogtreecommitdiffstats
path: root/drivers/sensors/pmw3320.c
blob: a4648ef425b0b87409c780aac92c76ec49fa731a (plain)
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
/* Copyright 2021 Colin Lam (Ploopy Corporation)
 * Copyright 2020 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
 * Copyright 2019 Sunjun Kim
 * Copyright 2019 Hiroyuki Okada
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pmw3320.h"
#include "wait.h"
#include "debug.h"
#include "gpio.h"

void pmw3320_init(void) {
    // Initialize sensor serial pins.
    setPinOutput(PMW3320_SCLK_PIN);
    setPinOutput(PMW3320_SDIO_PIN);
    setPinOutput(PMW3320_CS_PIN);

    // reboot the sensor.
    pmw3320_write_reg(REG_Power_Up_Reset, 0x5a);

    // wait maximum time before sensor is ready.
    // this ensures that the sensor is actually ready after reset.
    wait_ms(55);

    // read a burst from the sensor and then discard it.
    // gets the sensor ready for write commands
    // (for example, setting the dpi).
    pmw3320_read_burst();

    // Pretty sure that this shouldn't be in the driver.
    // Probably device specific?
    // Set rest mode to default
    pmw3320_write_reg(REG_Rest_Mode_Status, 0x00);
    // Set LED to be always on
    pmw3320_write_reg(REG_Led_Control, 0x4);
    // Disable rest mode
    pmw3320_write_reg(REG_Performance, 0x80);
}

// Perform a synchronization with sensor.
// Just as with the serial protocol, this is used by the slave to send a
// synchronization signal to the master.
void pmw3320_sync(void) {
    writePinLow(PMW3320_CS_PIN);
    wait_us(1);
    writePinHigh(PMW3320_CS_PIN);
}

void pmw3320_cs_select(void) {
    writePinLow(PMW3320_CS_PIN);
}

void pmw3320_cs_deselect(void) {
    writePinHigh(PMW3320_CS_PIN);
}

uint8_t pmw3320_serial_read(void) {
    setPinInput(PMW3320_SDIO_PIN);
    uint8_t byte = 0;

    for (uint8_t i = 0; i < 8; ++i) {
        writePinLow(PMW3320_SCLK_PIN);
        wait_us(1);

        byte = (byte << 1) | readPin(PMW3320_SDIO_PIN);

        writePinHigh(PMW3320_SCLK_PIN);
        wait_us(1);
    }

    return byte;
}

void pmw3320_serial_write(uint8_t data) {
    setPinOutput(PMW3320_SDIO_PIN);

    for (int8_t b = 7; b >= 0; b--) {
        writePinLow(PMW3320_SCLK_PIN);

        if (data & (1 << b))
            writePinHigh(PMW3320_SDIO_PIN);
        else
            writePinLow(PMW3320_SDIO_PIN);

        wait_us(2);

        writePinHigh(PMW3320_SCLK_PIN);
    }

    // This was taken from ADNS5050 driver.
    // There's no any info in PMW3320 datasheet about this...
    // tSWR. See page 15 of the ADNS5050 spec sheet.
    // Technically, this is only necessary if the next operation is an SDIO
    // read. This is not guaranteed to be the case, but we're being lazy.
    wait_us(4);

    // Note that tSWW is never necessary. All write operations require at
    // least 32us, which exceeds tSWW, so there's never a need to wait for it.
}

// Read a byte of data from a register on the sensor.
uint8_t pmw3320_read_reg(uint8_t reg_addr) {
    pmw3320_cs_select();

    pmw3320_serial_write(reg_addr);

    uint8_t byte = pmw3320_serial_read();

    // This was taken directly from ADNS5050 driver...
    // tSRW & tSRR. See page 15 of the ADNS5050 spec sheet.
    // Technically, this is only necessary if the next operation is an SDIO
    // read or write. This is not guaranteed to be the case.
    // Honestly, this wait could probably be removed.
    wait_us(1);

    pmw3320_cs_deselect();

    return byte;
}

void pmw3320_write_reg(uint8_t reg_addr, uint8_t data) {
    pmw3320_cs_select();
    pmw3320_serial_write(0b10000000 | reg_addr);
    pmw3320_serial_write(data);
    pmw3320_cs_deselect();
}

report_pmw3320_t pmw3320_read_burst(void) {
    pmw3320_cs_select();

    report_pmw3320_t data;
    data.dx = 0;
    data.dy = 0;

    pmw3320_serial_write(REG_Motion_Burst);

    uint8_t x = pmw3320_serial_read();
    uint8_t y = pmw3320_serial_read();

    // Probably burst mode may include contents of delta_xy register,
    // which contain HI parts of x/y deltas, but I had no luck finding it.
    // Probably it's required to activate 12-bit mode to access this data.
    // So we end burst mode early to not read unneeded information.
    pmw3320_cs_deselect();

    data.dx = convert_twoscomp(x);
    data.dy = convert_twoscomp(y);

    return data;
}

// Convert a two's complement byte from an unsigned data type into a signed
// data type.
int8_t convert_twoscomp(uint8_t data) {
    if ((data & 0x80) == 0x80)
        return -128 + (data & 0x7F);
    else
        return data;
}

uint16_t pmw3320_get_cpi(void) {
    uint8_t cpival = pmw3320_read_reg(REG_Resolution);
    // 0x1F is an inversion of 0x20 which is 0b100000
    return (uint16_t)((cpival & 0x1F) * PMW3320_CPI_STEP);
}

void pmw3320_set_cpi(uint16_t cpi) {
    uint8_t cpival = constrain((cpi / PMW3320_CPI_STEP) - 1U, 0, (PMW3320_CPI_MAX / PMW3320_CPI_STEP) - 1U);
    // Fifth bit is probably a control bit.
    // PMW3320 datasheet don't have any info on this, so this is a pure guess.
    pmw3320_write_reg(REG_Resolution, 0x20 | cpival);
}

bool pmw3320_check_signature(void) {
    uint8_t pid  = pmw3320_read_reg(REG_Product_ID);
    uint8_t pid2 = pmw3320_read_reg(REG_Inverse_Product_ID);

    return (pid == 0x3b && pid2 == 0xc4);
}