Merged from external project:
[bertos.git] / bertos / drv / i2c_bitbang.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
30  *
31  * -->
32  *
33  * \brief I2C bitbang driver (implementation)
34  *
35  * \version $Id: adc.c 1604 2008-08-10 17:19:51Z bernie $
36  * \author Francesco Sacchi <batt@develer.com>
37  */
38
39 #include "i2c.h"
40 #include "timer.h"
41 #include "hw/hw_i2c_bitbang.h"
42
43 #define I2C_PERIOD DIV_ROUND(500000UL / CONFIG_I2C_FREQ)
44
45 INLINE bool i2c_start(void)
46 {
47         SDA_HI;
48         SCL_HI;
49         timer_udelay(I2C_PERIOD);
50         SDA_LOW;
51         timer_udelay(I2C_PERIOD);
52         return !SDA;
53 }
54
55 void i2c_stop(void)
56 {
57         SCL_HI;
58         timer_udelay(I2C_PERIOD);
59         SDA_HI;
60 }
61
62 bool i2c_put(uint8_t _data)
63 {
64         uint16_t data = (_data << 1) | 1;
65         for (uint16_t i = 0x100; i >= 0; i >>= 1)
66         {
67                 SCL_LO;
68                 timer_udelay(I2C_PERIOD);
69                 if (data & i)
70                         SDA_HI;
71                 else
72                         SDA_LO;
73                 SCL_HI;
74                 timer_udelay(I2C_PERIOD);
75                 ASSERT(SDA == (data & i));
76         }
77         bool ack = !SDA;
78         ASSERT(ack);
79         SCL_LO;
80         timer_udelay(I2C_PERIOD);
81         return ack;
82 }
83
84 bool i2c_start_w(uint8_t id)
85 {
86         id &= ~I2C_READBIT;
87         /*
88          * Loop on the select write sequence: when the device is busy
89          * writing previously sent data it will reply to the SLA_W
90          * control byte with a NACK.  In this case, we must
91          * keep trying until the deveice responds with an ACK.
92          */
93         ticks_t start = timer_clock();
94         while (i2c_start())
95         {
96                 if (i2c_put(id))
97                         return true;
98                 else if (timer_clock() - start > ms_to_ticks(CONFIG_TWI_START_TIMEOUT))
99                 {
100                         LOG_ERR("Timeout on I2C start write\n");
101                         break;
102                 }
103         }
104
105         return false;
106 }
107
108 bool i2c_start_r(uint8_t id)
109 {
110         id |= I2C_READBIT;
111         if (i2c_start())
112         {
113                 if (i2c_put(id))
114                         return true;
115
116                 LOG_ERR("NACK on I2c start read\n");
117         }
118
119         return false;
120 }
121
122 int i2c_get(bool ack)
123 {
124         uint8_t data = 0;
125         for (uint8_t i = 0x80; i >= 0; i >>= 1)
126         {
127                 SCL_LO;
128                 timer_udelay(I2C_PERIOD);
129                 SCL_HI;
130                 timer_udelay(I2C_PERIOD);
131                 if (SDA)
132                         data |= i;
133         }
134         SCL_LO;
135         timer_udelay(I2C_PERIOD);
136
137         if (ack)
138                 SDA_LO;
139         else
140                 SDA_HI;
141
142         SCL_HI;
143         /* avoid sign extension */
144         return (int)(uint8_t)data;
145 }
146
147 MOD_DEFINE(i2c);
148
149 /**
150  * Initialize i2c module.
151  */
152 void i2c_init(void)
153 {
154         I2C_BITBANG_HW_INIT;
155         SDA_HI;
156         SCL_HI;
157         MOD_INIT(i2c);
158 }
159