Blame view
drivers/mfd/tc3589x.c
11.8 KB
1f67b599f
|
1 |
// SPDX-License-Identifier: GPL-2.0-only |
b4ecd326b
|
2 3 4 |
/* * Copyright (C) ST-Ericsson SA 2010 * |
b4ecd326b
|
5 6 7 8 9 10 11 |
* Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson */ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/irq.h> |
15e27b108
|
12 |
#include <linux/irqdomain.h> |
b4ecd326b
|
13 14 |
#include <linux/slab.h> #include <linux/i2c.h> |
a435ae1d5
|
15 |
#include <linux/of.h> |
a381b13e2
|
16 |
#include <linux/of_device.h> |
b4ecd326b
|
17 |
#include <linux/mfd/core.h> |
c6eda6c5e
|
18 |
#include <linux/mfd/tc3589x.h> |
a381b13e2
|
19 |
#include <linux/err.h> |
b4ecd326b
|
20 |
|
e64c1eb47
|
21 22 23 24 25 26 27 28 29 30 31 32 |
/** * enum tc3589x_version - indicates the TC3589x version */ enum tc3589x_version { TC3589X_TC35890, TC3589X_TC35892, TC3589X_TC35893, TC3589X_TC35894, TC3589X_TC35895, TC3589X_TC35896, TC3589X_UNKNOWN, }; |
593e9d70f
|
33 34 |
#define TC3589x_CLKMODE_MODCTL_SLEEP 0x0 #define TC3589x_CLKMODE_MODCTL_OPERATION (1 << 0) |
b4ecd326b
|
35 |
/** |
20406ebff
|
36 37 |
* tc3589x_reg_read() - read a single TC3589x register * @tc3589x: Device to read from |
b4ecd326b
|
38 39 |
* @reg: Register to read */ |
20406ebff
|
40 |
int tc3589x_reg_read(struct tc3589x *tc3589x, u8 reg) |
b4ecd326b
|
41 42 |
{ int ret; |
20406ebff
|
43 |
ret = i2c_smbus_read_byte_data(tc3589x->i2c, reg); |
b4ecd326b
|
44 |
if (ret < 0) |
20406ebff
|
45 46 |
dev_err(tc3589x->dev, "failed to read reg %#x: %d ", |
b4ecd326b
|
47 48 49 50 |
reg, ret); return ret; } |
20406ebff
|
51 |
EXPORT_SYMBOL_GPL(tc3589x_reg_read); |
b4ecd326b
|
52 53 |
/** |
d87814a3e
|
54 |
* tc3589x_reg_write() - write a single TC3589x register |
20406ebff
|
55 |
* @tc3589x: Device to write to |
b4ecd326b
|
56 57 58 |
* @reg: Register to read * @data: Value to write */ |
20406ebff
|
59 |
int tc3589x_reg_write(struct tc3589x *tc3589x, u8 reg, u8 data) |
b4ecd326b
|
60 61 |
{ int ret; |
20406ebff
|
62 |
ret = i2c_smbus_write_byte_data(tc3589x->i2c, reg, data); |
b4ecd326b
|
63 |
if (ret < 0) |
20406ebff
|
64 65 |
dev_err(tc3589x->dev, "failed to write reg %#x: %d ", |
b4ecd326b
|
66 67 68 69 |
reg, ret); return ret; } |
20406ebff
|
70 |
EXPORT_SYMBOL_GPL(tc3589x_reg_write); |
b4ecd326b
|
71 72 |
/** |
20406ebff
|
73 74 |
* tc3589x_block_read() - read multiple TC3589x registers * @tc3589x: Device to read from |
b4ecd326b
|
75 76 77 78 |
* @reg: First register * @length: Number of registers * @values: Buffer to write to */ |
20406ebff
|
79 |
int tc3589x_block_read(struct tc3589x *tc3589x, u8 reg, u8 length, u8 *values) |
b4ecd326b
|
80 81 |
{ int ret; |
20406ebff
|
82 |
ret = i2c_smbus_read_i2c_block_data(tc3589x->i2c, reg, length, values); |
b4ecd326b
|
83 |
if (ret < 0) |
20406ebff
|
84 85 |
dev_err(tc3589x->dev, "failed to read regs %#x: %d ", |
b4ecd326b
|
86 87 88 89 |
reg, ret); return ret; } |
20406ebff
|
90 |
EXPORT_SYMBOL_GPL(tc3589x_block_read); |
b4ecd326b
|
91 92 |
/** |
20406ebff
|
93 94 |
* tc3589x_block_write() - write multiple TC3589x registers * @tc3589x: Device to write to |
b4ecd326b
|
95 96 97 98 |
* @reg: First register * @length: Number of registers * @values: Values to write */ |
20406ebff
|
99 |
int tc3589x_block_write(struct tc3589x *tc3589x, u8 reg, u8 length, |
b4ecd326b
|
100 101 102 |
const u8 *values) { int ret; |
20406ebff
|
103 |
ret = i2c_smbus_write_i2c_block_data(tc3589x->i2c, reg, length, |
b4ecd326b
|
104 105 |
values); if (ret < 0) |
20406ebff
|
106 107 |
dev_err(tc3589x->dev, "failed to write regs %#x: %d ", |
b4ecd326b
|
108 109 110 111 |
reg, ret); return ret; } |
20406ebff
|
112 |
EXPORT_SYMBOL_GPL(tc3589x_block_write); |
b4ecd326b
|
113 114 |
/** |
20406ebff
|
115 116 |
* tc3589x_set_bits() - set the value of a bitfield in a TC3589x register * @tc3589x: Device to write to |
b4ecd326b
|
117 118 |
* @reg: Register to write * @mask: Mask of bits to set |
d87814a3e
|
119 |
* @val: Value to set |
b4ecd326b
|
120 |
*/ |
20406ebff
|
121 |
int tc3589x_set_bits(struct tc3589x *tc3589x, u8 reg, u8 mask, u8 val) |
b4ecd326b
|
122 123 |
{ int ret; |
20406ebff
|
124 |
mutex_lock(&tc3589x->lock); |
b4ecd326b
|
125 |
|
20406ebff
|
126 |
ret = tc3589x_reg_read(tc3589x, reg); |
b4ecd326b
|
127 128 129 130 131 |
if (ret < 0) goto out; ret &= ~mask; ret |= val; |
20406ebff
|
132 |
ret = tc3589x_reg_write(tc3589x, reg, ret); |
b4ecd326b
|
133 134 |
out: |
20406ebff
|
135 |
mutex_unlock(&tc3589x->lock); |
b4ecd326b
|
136 137 |
return ret; } |
20406ebff
|
138 |
EXPORT_SYMBOL_GPL(tc3589x_set_bits); |
b4ecd326b
|
139 140 141 |
static struct resource gpio_resources[] = { { |
20406ebff
|
142 143 |
.start = TC3589x_INT_GPIIRQ, .end = TC3589x_INT_GPIIRQ, |
b4ecd326b
|
144 145 146 |
.flags = IORESOURCE_IRQ, }, }; |
09c730a48
|
147 148 149 150 151 152 153 |
static struct resource keypad_resources[] = { { .start = TC3589x_INT_KBDIRQ, .end = TC3589x_INT_KBDIRQ, .flags = IORESOURCE_IRQ, }, }; |
afb580a94
|
154 |
static const struct mfd_cell tc3589x_dev_gpio[] = { |
b4ecd326b
|
155 |
{ |
20406ebff
|
156 |
.name = "tc3589x-gpio", |
b4ecd326b
|
157 158 |
.num_resources = ARRAY_SIZE(gpio_resources), .resources = &gpio_resources[0], |
a381b13e2
|
159 |
.of_compatible = "toshiba,tc3589x-gpio", |
b4ecd326b
|
160 161 |
}, }; |
afb580a94
|
162 |
static const struct mfd_cell tc3589x_dev_keypad[] = { |
09c730a48
|
163 164 165 166 |
{ .name = "tc3589x-keypad", .num_resources = ARRAY_SIZE(keypad_resources), .resources = &keypad_resources[0], |
a381b13e2
|
167 |
.of_compatible = "toshiba,tc3589x-keypad", |
09c730a48
|
168 169 |
}, }; |
20406ebff
|
170 |
static irqreturn_t tc3589x_irq(int irq, void *data) |
b4ecd326b
|
171 |
{ |
20406ebff
|
172 |
struct tc3589x *tc3589x = data; |
b4ecd326b
|
173 |
int status; |
bd77efd0c
|
174 |
again: |
20406ebff
|
175 |
status = tc3589x_reg_read(tc3589x, TC3589x_IRQST); |
b4ecd326b
|
176 177 178 179 180 |
if (status < 0) return IRQ_NONE; while (status) { int bit = __ffs(status); |
15e27b108
|
181 |
int virq = irq_create_mapping(tc3589x->domain, bit); |
b4ecd326b
|
182 |
|
15e27b108
|
183 |
handle_nested_irq(virq); |
b4ecd326b
|
184 185 186 187 188 189 |
status &= ~(1 << bit); } /* * A dummy read or write (to any register) appears to be necessary to * have the last interrupt clear (for example, GPIO IC write) take |
bd77efd0c
|
190 191 |
* effect. In such a case, recheck for any interrupt which is still * pending. |
b4ecd326b
|
192 |
*/ |
bd77efd0c
|
193 194 195 |
status = tc3589x_reg_read(tc3589x, TC3589x_IRQST); if (status) goto again; |
b4ecd326b
|
196 197 198 |
return IRQ_HANDLED; } |
15e27b108
|
199 200 |
static int tc3589x_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq) |
b4ecd326b
|
201 |
{ |
15e27b108
|
202 |
struct tc3589x *tc3589x = d->host_data; |
b4ecd326b
|
203 |
|
15e27b108
|
204 205 206 207 |
irq_set_chip_data(virq, tc3589x); irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_edge_irq); irq_set_nested_thread(virq, 1); |
15e27b108
|
208 |
irq_set_noprobe(virq); |
b4ecd326b
|
209 210 211 |
return 0; } |
15e27b108
|
212 |
static void tc3589x_irq_unmap(struct irq_domain *d, unsigned int virq) |
b4ecd326b
|
213 |
{ |
15e27b108
|
214 215 216 |
irq_set_chip_and_handler(virq, NULL, NULL); irq_set_chip_data(virq, NULL); } |
7ce7b26f8
|
217 |
static const struct irq_domain_ops tc3589x_irq_ops = { |
1f0529b4d
|
218 |
.map = tc3589x_irq_map, |
15e27b108
|
219 |
.unmap = tc3589x_irq_unmap, |
627918ed1
|
220 |
.xlate = irq_domain_xlate_onecell, |
15e27b108
|
221 |
}; |
a435ae1d5
|
222 |
static int tc3589x_irq_init(struct tc3589x *tc3589x, struct device_node *np) |
15e27b108
|
223 |
{ |
1f0529b4d
|
224 |
tc3589x->domain = irq_domain_add_simple( |
90f2d0f7b
|
225 |
np, TC3589x_NR_INTERNAL_IRQS, 0, |
1f0529b4d
|
226 |
&tc3589x_irq_ops, tc3589x); |
15e27b108
|
227 228 229 230 231 232 233 234 |
if (!tc3589x->domain) { dev_err(tc3589x->dev, "Failed to create irqdomain "); return -ENOSYS; } return 0; |
b4ecd326b
|
235 |
} |
20406ebff
|
236 |
static int tc3589x_chip_init(struct tc3589x *tc3589x) |
b4ecd326b
|
237 238 |
{ int manf, ver, ret; |
20406ebff
|
239 |
manf = tc3589x_reg_read(tc3589x, TC3589x_MANFCODE); |
b4ecd326b
|
240 241 |
if (manf < 0) return manf; |
20406ebff
|
242 |
ver = tc3589x_reg_read(tc3589x, TC3589x_VERSION); |
b4ecd326b
|
243 244 |
if (ver < 0) return ver; |
20406ebff
|
245 246 247 |
if (manf != TC3589x_MANFCODE_MAGIC) { dev_err(tc3589x->dev, "unknown manufacturer: %#x ", manf); |
b4ecd326b
|
248 249 |
return -EINVAL; } |
20406ebff
|
250 251 |
dev_info(tc3589x->dev, "manufacturer: %#x, version: %#x ", manf, ver); |
b4ecd326b
|
252 |
|
523bc3820
|
253 254 255 256 257 |
/* * Put everything except the IRQ module into reset; * also spare the GPIO module for any pin initialization * done during pre-kernel boot */ |
20406ebff
|
258 259 260 |
ret = tc3589x_reg_write(tc3589x, TC3589x_RSTCTRL, TC3589x_RSTCTRL_TIMRST | TC3589x_RSTCTRL_ROTRST |
523bc3820
|
261 |
| TC3589x_RSTCTRL_KBDRST); |
b4ecd326b
|
262 263 264 265 |
if (ret < 0) return ret; /* Clear the reset interrupt. */ |
20406ebff
|
266 |
return tc3589x_reg_write(tc3589x, TC3589x_RSTINTCLR, 0x1); |
b4ecd326b
|
267 |
} |
f791be492
|
268 |
static int tc3589x_device_init(struct tc3589x *tc3589x) |
611b7590a
|
269 270 271 272 273 274 |
{ int ret = 0; unsigned int blocks = tc3589x->pdata->block; if (blocks & TC3589x_BLOCK_GPIO) { ret = mfd_add_devices(tc3589x->dev, -1, tc3589x_dev_gpio, |
55692af5e
|
275 |
ARRAY_SIZE(tc3589x_dev_gpio), NULL, |
90f2d0f7b
|
276 |
0, tc3589x->domain); |
611b7590a
|
277 278 279 280 281 282 283 284 |
if (ret) { dev_err(tc3589x->dev, "failed to add gpio child "); return ret; } dev_info(tc3589x->dev, "added gpio block "); } |
09c730a48
|
285 286 |
if (blocks & TC3589x_BLOCK_KEYPAD) { ret = mfd_add_devices(tc3589x->dev, -1, tc3589x_dev_keypad, |
55692af5e
|
287 |
ARRAY_SIZE(tc3589x_dev_keypad), NULL, |
90f2d0f7b
|
288 |
0, tc3589x->domain); |
09c730a48
|
289 290 291 292 293 294 295 296 |
if (ret) { dev_err(tc3589x->dev, "failed to keypad child "); return ret; } dev_info(tc3589x->dev, "added keypad block "); } |
611b7590a
|
297 |
|
09c730a48
|
298 |
return ret; |
611b7590a
|
299 |
} |
a381b13e2
|
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
static const struct of_device_id tc3589x_match[] = { /* Legacy compatible string */ { .compatible = "tc3589x", .data = (void *) TC3589X_UNKNOWN }, { .compatible = "toshiba,tc35890", .data = (void *) TC3589X_TC35890 }, { .compatible = "toshiba,tc35892", .data = (void *) TC3589X_TC35892 }, { .compatible = "toshiba,tc35893", .data = (void *) TC3589X_TC35893 }, { .compatible = "toshiba,tc35894", .data = (void *) TC3589X_TC35894 }, { .compatible = "toshiba,tc35895", .data = (void *) TC3589X_TC35895 }, { .compatible = "toshiba,tc35896", .data = (void *) TC3589X_TC35896 }, { } }; MODULE_DEVICE_TABLE(of, tc3589x_match); static struct tc3589x_platform_data * tc3589x_of_probe(struct device *dev, enum tc3589x_version *version) |
a435ae1d5
|
316 |
{ |
a381b13e2
|
317 318 |
struct device_node *np = dev->of_node; struct tc3589x_platform_data *pdata; |
a435ae1d5
|
319 |
struct device_node *child; |
a381b13e2
|
320 321 322 323 324 325 326 327 328 329 |
const struct of_device_id *of_id; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); of_id = of_match_device(tc3589x_match, dev); if (!of_id) return ERR_PTR(-ENODEV); *version = (enum tc3589x_version) of_id->data; |
a435ae1d5
|
330 331 |
for_each_child_of_node(np, child) { |
a381b13e2
|
332 |
if (of_device_is_compatible(child, "toshiba,tc3589x-gpio")) |
a435ae1d5
|
333 |
pdata->block |= TC3589x_BLOCK_GPIO; |
a381b13e2
|
334 |
if (of_device_is_compatible(child, "toshiba,tc3589x-keypad")) |
a435ae1d5
|
335 |
pdata->block |= TC3589x_BLOCK_KEYPAD; |
a435ae1d5
|
336 |
} |
a381b13e2
|
337 |
return pdata; |
a435ae1d5
|
338 |
} |
f791be492
|
339 |
static int tc3589x_probe(struct i2c_client *i2c, |
b4ecd326b
|
340 341 |
const struct i2c_device_id *id) { |
a435ae1d5
|
342 |
struct device_node *np = i2c->dev.of_node; |
a381b13e2
|
343 |
struct tc3589x_platform_data *pdata = dev_get_platdata(&i2c->dev); |
20406ebff
|
344 |
struct tc3589x *tc3589x; |
a381b13e2
|
345 |
enum tc3589x_version version; |
b4ecd326b
|
346 |
int ret; |
a435ae1d5
|
347 |
if (!pdata) { |
a381b13e2
|
348 349 |
pdata = tc3589x_of_probe(&i2c->dev, &version); if (IS_ERR(pdata)) { |
a435ae1d5
|
350 351 |
dev_err(&i2c->dev, "No platform data or DT found "); |
a381b13e2
|
352 |
return PTR_ERR(pdata); |
a435ae1d5
|
353 |
} |
a381b13e2
|
354 355 356 |
} else { /* When not probing from device tree we have this ID */ version = id->driver_data; |
a435ae1d5
|
357 |
} |
b4ecd326b
|
358 359 360 |
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) return -EIO; |
1383e00f7
|
361 362 |
tc3589x = devm_kzalloc(&i2c->dev, sizeof(struct tc3589x), GFP_KERNEL); |
20406ebff
|
363 |
if (!tc3589x) |
b4ecd326b
|
364 |
return -ENOMEM; |
20406ebff
|
365 |
mutex_init(&tc3589x->lock); |
b4ecd326b
|
366 |
|
20406ebff
|
367 368 369 |
tc3589x->dev = &i2c->dev; tc3589x->i2c = i2c; tc3589x->pdata = pdata; |
e64c1eb47
|
370 |
|
a381b13e2
|
371 |
switch (version) { |
e64c1eb47
|
372 373 374 375 376 377 378 379 380 381 382 383 384 |
case TC3589X_TC35893: case TC3589X_TC35895: case TC3589X_TC35896: tc3589x->num_gpio = 20; break; case TC3589X_TC35890: case TC3589X_TC35892: case TC3589X_TC35894: case TC3589X_UNKNOWN: default: tc3589x->num_gpio = 24; break; } |
b4ecd326b
|
385 |
|
20406ebff
|
386 |
i2c_set_clientdata(i2c, tc3589x); |
b4ecd326b
|
387 |
|
20406ebff
|
388 |
ret = tc3589x_chip_init(tc3589x); |
b4ecd326b
|
389 |
if (ret) |
1383e00f7
|
390 |
return ret; |
b4ecd326b
|
391 |
|
a435ae1d5
|
392 |
ret = tc3589x_irq_init(tc3589x, np); |
b4ecd326b
|
393 |
if (ret) |
1383e00f7
|
394 |
return ret; |
b4ecd326b
|
395 |
|
20406ebff
|
396 |
ret = request_threaded_irq(tc3589x->i2c->irq, NULL, tc3589x_irq, |
b4ecd326b
|
397 |
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
20406ebff
|
398 |
"tc3589x", tc3589x); |
b4ecd326b
|
399 |
if (ret) { |
20406ebff
|
400 401 |
dev_err(tc3589x->dev, "failed to request IRQ: %d ", ret); |
1383e00f7
|
402 |
return ret; |
b4ecd326b
|
403 |
} |
611b7590a
|
404 |
ret = tc3589x_device_init(tc3589x); |
b4ecd326b
|
405 |
if (ret) { |
611b7590a
|
406 407 |
dev_err(tc3589x->dev, "failed to add child devices "); |
1383e00f7
|
408 |
return ret; |
b4ecd326b
|
409 410 411 |
} return 0; |
b4ecd326b
|
412 |
} |
4740f73fe
|
413 |
static int tc3589x_remove(struct i2c_client *client) |
b4ecd326b
|
414 |
{ |
20406ebff
|
415 |
struct tc3589x *tc3589x = i2c_get_clientdata(client); |
b4ecd326b
|
416 |
|
20406ebff
|
417 |
mfd_remove_devices(tc3589x->dev); |
b4ecd326b
|
418 |
|
b4ecd326b
|
419 420 |
return 0; } |
930bf0229
|
421 |
#ifdef CONFIG_PM_SLEEP |
593e9d70f
|
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
static int tc3589x_suspend(struct device *dev) { struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; /* put the system to sleep mode */ if (!device_may_wakeup(&client->dev)) ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, TC3589x_CLKMODE_MODCTL_SLEEP); return ret; } static int tc3589x_resume(struct device *dev) { struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; /* enable the system into operation */ if (!device_may_wakeup(&client->dev)) ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, TC3589x_CLKMODE_MODCTL_OPERATION); return ret; } |
54d8e2c32
|
449 |
#endif |
593e9d70f
|
450 |
|
930bf0229
|
451 |
static SIMPLE_DEV_PM_OPS(tc3589x_dev_pm_ops, tc3589x_suspend, tc3589x_resume); |
20406ebff
|
452 |
static const struct i2c_device_id tc3589x_id[] = { |
e64c1eb47
|
453 454 455 456 457 458 459 |
{ "tc35890", TC3589X_TC35890 }, { "tc35892", TC3589X_TC35892 }, { "tc35893", TC3589X_TC35893 }, { "tc35894", TC3589X_TC35894 }, { "tc35895", TC3589X_TC35895 }, { "tc35896", TC3589X_TC35896 }, { "tc3589x", TC3589X_UNKNOWN }, |
b4ecd326b
|
460 461 |
{ } }; |
20406ebff
|
462 |
MODULE_DEVICE_TABLE(i2c, tc3589x_id); |
b4ecd326b
|
463 |
|
20406ebff
|
464 |
static struct i2c_driver tc3589x_driver = { |
a381b13e2
|
465 466 |
.driver = { .name = "tc3589x", |
a381b13e2
|
467 468 469 |
.pm = &tc3589x_dev_pm_ops, .of_match_table = of_match_ptr(tc3589x_match), }, |
20406ebff
|
470 |
.probe = tc3589x_probe, |
84449216b
|
471 |
.remove = tc3589x_remove, |
20406ebff
|
472 |
.id_table = tc3589x_id, |
b4ecd326b
|
473 |
}; |
20406ebff
|
474 |
static int __init tc3589x_init(void) |
b4ecd326b
|
475 |
{ |
20406ebff
|
476 |
return i2c_add_driver(&tc3589x_driver); |
b4ecd326b
|
477 |
} |
20406ebff
|
478 |
subsys_initcall(tc3589x_init); |
b4ecd326b
|
479 |
|
20406ebff
|
480 |
static void __exit tc3589x_exit(void) |
b4ecd326b
|
481 |
{ |
20406ebff
|
482 |
i2c_del_driver(&tc3589x_driver); |
b4ecd326b
|
483 |
} |
20406ebff
|
484 |
module_exit(tc3589x_exit); |
b4ecd326b
|
485 486 |
MODULE_LICENSE("GPL v2"); |
20406ebff
|
487 |
MODULE_DESCRIPTION("TC3589x MFD core driver"); |
b4ecd326b
|
488 |
MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); |