Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

i2c: add SLIMpro I2C device driver on APM X-Gene platform

Add SLIMpro I2C device driver on APM X-Gene platform. This I2C
device driver use the SLIMpro Mailbox driver to tunnel message to
the SLIMpro coprocessor to do the work of accessing I2C components.

Signed-off-by: Feng Kan <fkan@apm.com>
Signed-off-by: Hieu Le <hnle@apm.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>

authored by

Feng Kan and committed by
Wolfram Sang
f6505fba 9dcb0e7b

+494
+15
Documentation/devicetree/bindings/i2c/i2c-xgene-slimpro.txt
··· 1 + APM X-Gene SLIMpro Mailbox I2C Driver 2 + 3 + An I2C controller accessed over the "SLIMpro" mailbox. 4 + 5 + Required properties : 6 + 7 + - compatible : should be "apm,xgene-slimpro-i2c" 8 + - mboxes : use the label reference for the mailbox as the first parameter. 9 + The second parameter is the channel number. 10 + 11 + Example : 12 + i2cslimpro { 13 + compatible = "apm,xgene-slimpro-i2c"; 14 + mboxes = <&mailbox 0>; 15 + };
+9
drivers/i2c/busses/Kconfig
··· 1110 1110 connected there. This will work whatever the interface used to 1111 1111 talk to the EC (SPI, I2C or LPC). 1112 1112 1113 + config I2C_XGENE_SLIMPRO 1114 + tristate "APM X-Gene SoC I2C SLIMpro devices support" 1115 + depends on ARCH_XGENE && MAILBOX 1116 + help 1117 + Enable I2C bus access using the APM X-Gene SoC SLIMpro 1118 + co-processor. The I2C device access the I2C bus via the X-Gene 1119 + to SLIMpro (On chip coprocessor) mailbox mechanism. 1120 + If unsure, say N. 1121 + 1113 1122 config SCx200_ACB 1114 1123 tristate "Geode ACCESS.bus support" 1115 1124 depends on X86_32 && PCI
+1
drivers/i2c/busses/Makefile
··· 110 110 obj-$(CONFIG_I2C_OPAL) += i2c-opal.o 111 111 obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o 112 112 obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o 113 + obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o 113 114 obj-$(CONFIG_SCx200_ACB) += scx200_acb.o 114 115 115 116 ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
+469
drivers/i2c/busses/i2c-xgene-slimpro.c
··· 1 + /* 2 + * X-Gene SLIMpro I2C Driver 3 + * 4 + * Copyright (c) 2014, Applied Micro Circuits Corporation 5 + * Author: Feng Kan <fkan@apm.com> 6 + * Author: Hieu Le <hnle@apm.com> 7 + * 8 + * This program is free software; you can redistribute it and/or 9 + * modify it under the terms of the GNU General Public License as 10 + * published by the Free Software Foundation; either version 2 of 11 + * the License, or (at your option) any later version. 12 + * 13 + * This program is distributed in the hope that it will be useful, 14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 + * GNU General Public License for more details. 17 + * 18 + * You should have received a copy of the GNU General Public License 19 + * along with this program; if not, see <http://www.gnu.org/licenses/>. 20 + * 21 + * This driver provides support for X-Gene SLIMpro I2C device access 22 + * using the APM X-Gene SLIMpro mailbox driver. 23 + * 24 + */ 25 + #include <linux/acpi.h> 26 + #include <linux/dma-mapping.h> 27 + #include <linux/i2c.h> 28 + #include <linux/interrupt.h> 29 + #include <linux/mailbox_client.h> 30 + #include <linux/module.h> 31 + #include <linux/of.h> 32 + #include <linux/platform_device.h> 33 + #include <linux/version.h> 34 + 35 + #define MAILBOX_OP_TIMEOUT 1000 /* Operation time out in ms */ 36 + #define MAILBOX_I2C_INDEX 0 37 + #define SLIMPRO_IIC_BUS 1 /* Use I2C bus 1 only */ 38 + 39 + #define SMBUS_CMD_LEN 1 40 + #define BYTE_DATA 1 41 + #define WORD_DATA 2 42 + #define BLOCK_DATA 3 43 + 44 + #define SLIMPRO_IIC_I2C_PROTOCOL 0 45 + #define SLIMPRO_IIC_SMB_PROTOCOL 1 46 + 47 + #define SLIMPRO_IIC_READ 0 48 + #define SLIMPRO_IIC_WRITE 1 49 + 50 + #define IIC_SMB_WITHOUT_DATA_LEN 0 51 + #define IIC_SMB_WITH_DATA_LEN 1 52 + 53 + #define SLIMPRO_DEBUG_MSG 0 54 + #define SLIMPRO_MSG_TYPE_SHIFT 28 55 + #define SLIMPRO_DBG_SUBTYPE_I2C1READ 4 56 + #define SLIMPRO_DBGMSG_TYPE_SHIFT 24 57 + #define SLIMPRO_DBGMSG_TYPE_MASK 0x0F000000U 58 + #define SLIMPRO_IIC_DEV_SHIFT 23 59 + #define SLIMPRO_IIC_DEV_MASK 0x00800000U 60 + #define SLIMPRO_IIC_DEVID_SHIFT 13 61 + #define SLIMPRO_IIC_DEVID_MASK 0x007FE000U 62 + #define SLIMPRO_IIC_RW_SHIFT 12 63 + #define SLIMPRO_IIC_RW_MASK 0x00001000U 64 + #define SLIMPRO_IIC_PROTO_SHIFT 11 65 + #define SLIMPRO_IIC_PROTO_MASK 0x00000800U 66 + #define SLIMPRO_IIC_ADDRLEN_SHIFT 8 67 + #define SLIMPRO_IIC_ADDRLEN_MASK 0x00000700U 68 + #define SLIMPRO_IIC_DATALEN_SHIFT 0 69 + #define SLIMPRO_IIC_DATALEN_MASK 0x000000FFU 70 + 71 + /* 72 + * SLIMpro I2C message encode 73 + * 74 + * dev - Controller number (0-based) 75 + * chip - I2C chip address 76 + * op - SLIMPRO_IIC_READ or SLIMPRO_IIC_WRITE 77 + * proto - SLIMPRO_IIC_SMB_PROTOCOL or SLIMPRO_IIC_I2C_PROTOCOL 78 + * addrlen - Length of the address field 79 + * datalen - Length of the data field 80 + */ 81 + #define SLIMPRO_IIC_ENCODE_MSG(dev, chip, op, proto, addrlen, datalen) \ 82 + ((SLIMPRO_DEBUG_MSG << SLIMPRO_MSG_TYPE_SHIFT) | \ 83 + ((SLIMPRO_DBG_SUBTYPE_I2C1READ << SLIMPRO_DBGMSG_TYPE_SHIFT) & \ 84 + SLIMPRO_DBGMSG_TYPE_MASK) | \ 85 + ((dev << SLIMPRO_IIC_DEV_SHIFT) & SLIMPRO_IIC_DEV_MASK) | \ 86 + ((chip << SLIMPRO_IIC_DEVID_SHIFT) & SLIMPRO_IIC_DEVID_MASK) | \ 87 + ((op << SLIMPRO_IIC_RW_SHIFT) & SLIMPRO_IIC_RW_MASK) | \ 88 + ((proto << SLIMPRO_IIC_PROTO_SHIFT) & SLIMPRO_IIC_PROTO_MASK) | \ 89 + ((addrlen << SLIMPRO_IIC_ADDRLEN_SHIFT) & SLIMPRO_IIC_ADDRLEN_MASK) | \ 90 + ((datalen << SLIMPRO_IIC_DATALEN_SHIFT) & SLIMPRO_IIC_DATALEN_MASK)) 91 + 92 + /* 93 + * Encode for upper address for block data 94 + */ 95 + #define SLIMPRO_IIC_ENCODE_FLAG_BUFADDR 0x80000000 96 + #define SLIMPRO_IIC_ENCODE_FLAG_WITH_DATA_LEN(a) ((u32) (((a) << 30) \ 97 + & 0x40000000)) 98 + #define SLIMPRO_IIC_ENCODE_UPPER_BUFADDR(a) ((u32) (((a) >> 12) \ 99 + & 0x3FF00000)) 100 + #define SLIMPRO_IIC_ENCODE_ADDR(a) ((a) & 0x000FFFFF) 101 + 102 + struct slimpro_i2c_dev { 103 + struct i2c_adapter adapter; 104 + struct device *dev; 105 + struct mbox_chan *mbox_chan; 106 + struct mbox_client mbox_client; 107 + struct completion rd_complete; 108 + u8 dma_buffer[I2C_SMBUS_BLOCK_MAX]; 109 + u32 *resp_msg; 110 + }; 111 + 112 + #define to_slimpro_i2c_dev(cl) \ 113 + container_of(cl, struct slimpro_i2c_dev, mbox_client) 114 + 115 + static void slimpro_i2c_rx_cb(struct mbox_client *cl, void *mssg) 116 + { 117 + struct slimpro_i2c_dev *ctx = to_slimpro_i2c_dev(cl); 118 + 119 + /* 120 + * Response message format: 121 + * mssg[0] is the return code of the operation 122 + * mssg[1] is the first data word 123 + * mssg[2] is NOT used 124 + */ 125 + if (ctx->resp_msg) 126 + *ctx->resp_msg = ((u32 *)mssg)[1]; 127 + 128 + if (ctx->mbox_client.tx_block) 129 + complete(&ctx->rd_complete); 130 + } 131 + 132 + static int start_i2c_msg_xfer(struct slimpro_i2c_dev *ctx) 133 + { 134 + if (ctx->mbox_client.tx_block) { 135 + if (!wait_for_completion_timeout(&ctx->rd_complete, 136 + msecs_to_jiffies(MAILBOX_OP_TIMEOUT))) 137 + return -ETIMEDOUT; 138 + } 139 + 140 + /* Check of invalid data or no device */ 141 + if (*ctx->resp_msg == 0xffffffff) 142 + return -ENODEV; 143 + 144 + return 0; 145 + } 146 + 147 + static int slimpro_i2c_rd(struct slimpro_i2c_dev *ctx, u32 chip, 148 + u32 addr, u32 addrlen, u32 protocol, 149 + u32 readlen, u32 *data) 150 + { 151 + u32 msg[3]; 152 + int rc; 153 + 154 + msg[0] = SLIMPRO_IIC_ENCODE_MSG(SLIMPRO_IIC_BUS, chip, 155 + SLIMPRO_IIC_READ, protocol, addrlen, readlen); 156 + msg[1] = SLIMPRO_IIC_ENCODE_ADDR(addr); 157 + msg[2] = 0; 158 + ctx->resp_msg = data; 159 + rc = mbox_send_message(ctx->mbox_chan, &msg); 160 + if (rc < 0) 161 + goto err; 162 + 163 + rc = start_i2c_msg_xfer(ctx); 164 + err: 165 + ctx->resp_msg = NULL; 166 + return rc; 167 + } 168 + 169 + static int slimpro_i2c_wr(struct slimpro_i2c_dev *ctx, u32 chip, 170 + u32 addr, u32 addrlen, u32 protocol, u32 writelen, 171 + u32 data) 172 + { 173 + u32 msg[3]; 174 + int rc; 175 + 176 + msg[0] = SLIMPRO_IIC_ENCODE_MSG(SLIMPRO_IIC_BUS, chip, 177 + SLIMPRO_IIC_WRITE, protocol, addrlen, writelen); 178 + msg[1] = SLIMPRO_IIC_ENCODE_ADDR(addr); 179 + msg[2] = data; 180 + ctx->resp_msg = msg; 181 + 182 + rc = mbox_send_message(ctx->mbox_chan, &msg); 183 + if (rc < 0) 184 + goto err; 185 + 186 + rc = start_i2c_msg_xfer(ctx); 187 + err: 188 + ctx->resp_msg = NULL; 189 + return rc; 190 + } 191 + 192 + static int slimpro_i2c_blkrd(struct slimpro_i2c_dev *ctx, u32 chip, u32 addr, 193 + u32 addrlen, u32 protocol, u32 readlen, 194 + u32 with_data_len, void *data) 195 + { 196 + dma_addr_t paddr; 197 + u32 msg[3]; 198 + int rc; 199 + 200 + paddr = dma_map_single(ctx->dev, ctx->dma_buffer, readlen, DMA_FROM_DEVICE); 201 + rc = dma_mapping_error(ctx->dev, paddr); 202 + if (rc) { 203 + dev_err(&ctx->adapter.dev, "Error in mapping dma buffer %p\n", 204 + ctx->dma_buffer); 205 + goto err; 206 + } 207 + 208 + msg[0] = SLIMPRO_IIC_ENCODE_MSG(SLIMPRO_IIC_BUS, chip, SLIMPRO_IIC_READ, 209 + protocol, addrlen, readlen); 210 + msg[1] = SLIMPRO_IIC_ENCODE_FLAG_BUFADDR | 211 + SLIMPRO_IIC_ENCODE_FLAG_WITH_DATA_LEN(with_data_len) | 212 + SLIMPRO_IIC_ENCODE_UPPER_BUFADDR(paddr) | 213 + SLIMPRO_IIC_ENCODE_ADDR(addr); 214 + msg[2] = (u32)paddr; 215 + ctx->resp_msg = msg; 216 + 217 + rc = mbox_send_message(ctx->mbox_chan, &msg); 218 + if (rc < 0) 219 + goto err_unmap; 220 + 221 + rc = start_i2c_msg_xfer(ctx); 222 + 223 + /* Copy to destination */ 224 + memcpy(data, ctx->dma_buffer, readlen); 225 + 226 + err_unmap: 227 + dma_unmap_single(ctx->dev, paddr, readlen, DMA_FROM_DEVICE); 228 + err: 229 + ctx->resp_msg = NULL; 230 + return rc; 231 + } 232 + 233 + static int slimpro_i2c_blkwr(struct slimpro_i2c_dev *ctx, u32 chip, 234 + u32 addr, u32 addrlen, u32 protocol, u32 writelen, 235 + void *data) 236 + { 237 + dma_addr_t paddr; 238 + u32 msg[3]; 239 + int rc; 240 + 241 + memcpy(ctx->dma_buffer, data, writelen); 242 + paddr = dma_map_single(ctx->dev, ctx->dma_buffer, writelen, 243 + DMA_TO_DEVICE); 244 + rc = dma_mapping_error(ctx->dev, paddr); 245 + if (rc) { 246 + dev_err(&ctx->adapter.dev, "Error in mapping dma buffer %p\n", 247 + ctx->dma_buffer); 248 + goto err; 249 + } 250 + 251 + msg[0] = SLIMPRO_IIC_ENCODE_MSG(SLIMPRO_IIC_BUS, chip, SLIMPRO_IIC_WRITE, 252 + protocol, addrlen, writelen); 253 + msg[1] = SLIMPRO_IIC_ENCODE_FLAG_BUFADDR | 254 + SLIMPRO_IIC_ENCODE_UPPER_BUFADDR(paddr) | 255 + SLIMPRO_IIC_ENCODE_ADDR(addr); 256 + msg[2] = (u32)paddr; 257 + ctx->resp_msg = msg; 258 + 259 + if (ctx->mbox_client.tx_block) 260 + reinit_completion(&ctx->rd_complete); 261 + 262 + rc = mbox_send_message(ctx->mbox_chan, &msg); 263 + if (rc < 0) 264 + goto err_unmap; 265 + 266 + rc = start_i2c_msg_xfer(ctx); 267 + 268 + err_unmap: 269 + dma_unmap_single(ctx->dev, paddr, writelen, DMA_TO_DEVICE); 270 + err: 271 + ctx->resp_msg = NULL; 272 + return rc; 273 + } 274 + 275 + static int xgene_slimpro_i2c_xfer(struct i2c_adapter *adap, u16 addr, 276 + unsigned short flags, char read_write, 277 + u8 command, int size, 278 + union i2c_smbus_data *data) 279 + { 280 + struct slimpro_i2c_dev *ctx = i2c_get_adapdata(adap); 281 + int ret = -EOPNOTSUPP; 282 + u32 val; 283 + 284 + switch (size) { 285 + case I2C_SMBUS_BYTE: 286 + if (read_write == I2C_SMBUS_READ) { 287 + ret = slimpro_i2c_rd(ctx, addr, 0, 0, 288 + SLIMPRO_IIC_SMB_PROTOCOL, 289 + BYTE_DATA, &val); 290 + data->byte = val; 291 + } else { 292 + ret = slimpro_i2c_wr(ctx, addr, command, SMBUS_CMD_LEN, 293 + SLIMPRO_IIC_SMB_PROTOCOL, 294 + 0, 0); 295 + } 296 + break; 297 + case I2C_SMBUS_BYTE_DATA: 298 + if (read_write == I2C_SMBUS_READ) { 299 + ret = slimpro_i2c_rd(ctx, addr, command, SMBUS_CMD_LEN, 300 + SLIMPRO_IIC_SMB_PROTOCOL, 301 + BYTE_DATA, &val); 302 + data->byte = val; 303 + } else { 304 + val = data->byte; 305 + ret = slimpro_i2c_wr(ctx, addr, command, SMBUS_CMD_LEN, 306 + SLIMPRO_IIC_SMB_PROTOCOL, 307 + BYTE_DATA, val); 308 + } 309 + break; 310 + case I2C_SMBUS_WORD_DATA: 311 + if (read_write == I2C_SMBUS_READ) { 312 + ret = slimpro_i2c_rd(ctx, addr, command, SMBUS_CMD_LEN, 313 + SLIMPRO_IIC_SMB_PROTOCOL, 314 + WORD_DATA, &val); 315 + data->word = val; 316 + } else { 317 + val = data->word; 318 + ret = slimpro_i2c_wr(ctx, addr, command, SMBUS_CMD_LEN, 319 + SLIMPRO_IIC_SMB_PROTOCOL, 320 + WORD_DATA, val); 321 + } 322 + break; 323 + case I2C_SMBUS_BLOCK_DATA: 324 + if (read_write == I2C_SMBUS_READ) { 325 + ret = slimpro_i2c_blkrd(ctx, addr, command, 326 + SMBUS_CMD_LEN, 327 + SLIMPRO_IIC_SMB_PROTOCOL, 328 + I2C_SMBUS_BLOCK_MAX + 1, 329 + IIC_SMB_WITH_DATA_LEN, 330 + &data->block[0]); 331 + 332 + } else { 333 + ret = slimpro_i2c_blkwr(ctx, addr, command, 334 + SMBUS_CMD_LEN, 335 + SLIMPRO_IIC_SMB_PROTOCOL, 336 + data->block[0] + 1, 337 + &data->block[0]); 338 + } 339 + break; 340 + case I2C_SMBUS_I2C_BLOCK_DATA: 341 + if (read_write == I2C_SMBUS_READ) { 342 + ret = slimpro_i2c_blkrd(ctx, addr, 343 + command, 344 + SMBUS_CMD_LEN, 345 + SLIMPRO_IIC_I2C_PROTOCOL, 346 + I2C_SMBUS_BLOCK_MAX, 347 + IIC_SMB_WITHOUT_DATA_LEN, 348 + &data->block[1]); 349 + } else { 350 + ret = slimpro_i2c_blkwr(ctx, addr, command, 351 + SMBUS_CMD_LEN, 352 + SLIMPRO_IIC_I2C_PROTOCOL, 353 + data->block[0], 354 + &data->block[1]); 355 + } 356 + break; 357 + default: 358 + break; 359 + } 360 + return ret; 361 + } 362 + 363 + /* 364 + * Return list of supported functionality. 365 + */ 366 + static u32 xgene_slimpro_i2c_func(struct i2c_adapter *adapter) 367 + { 368 + return I2C_FUNC_SMBUS_BYTE | 369 + I2C_FUNC_SMBUS_BYTE_DATA | 370 + I2C_FUNC_SMBUS_WORD_DATA | 371 + I2C_FUNC_SMBUS_BLOCK_DATA | 372 + I2C_FUNC_SMBUS_I2C_BLOCK; 373 + } 374 + 375 + static struct i2c_algorithm xgene_slimpro_i2c_algorithm = { 376 + .smbus_xfer = xgene_slimpro_i2c_xfer, 377 + .functionality = xgene_slimpro_i2c_func, 378 + }; 379 + 380 + static int __init xgene_slimpro_i2c_probe(struct platform_device *pdev) 381 + { 382 + struct slimpro_i2c_dev *ctx; 383 + struct i2c_adapter *adapter; 384 + struct mbox_client *cl; 385 + int rc; 386 + 387 + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); 388 + if (!ctx) 389 + return -ENOMEM; 390 + 391 + ctx->dev = &pdev->dev; 392 + platform_set_drvdata(pdev, ctx); 393 + cl = &ctx->mbox_client; 394 + 395 + /* Request mailbox channel */ 396 + cl->dev = &pdev->dev; 397 + cl->rx_callback = slimpro_i2c_rx_cb; 398 + cl->tx_block = true; 399 + init_completion(&ctx->rd_complete); 400 + cl->tx_tout = MAILBOX_OP_TIMEOUT; 401 + cl->knows_txdone = false; 402 + ctx->mbox_chan = mbox_request_channel(cl, MAILBOX_I2C_INDEX); 403 + if (IS_ERR(ctx->mbox_chan)) { 404 + dev_err(&pdev->dev, "i2c mailbox channel request failed\n"); 405 + return PTR_ERR(ctx->mbox_chan); 406 + } 407 + 408 + rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); 409 + if (rc) 410 + dev_warn(&pdev->dev, "Unable to set dma mask\n"); 411 + 412 + /* Setup I2C adapter */ 413 + adapter = &ctx->adapter; 414 + snprintf(adapter->name, sizeof(adapter->name), "MAILBOX I2C"); 415 + adapter->algo = &xgene_slimpro_i2c_algorithm; 416 + adapter->class = I2C_CLASS_HWMON; 417 + adapter->dev.parent = &pdev->dev; 418 + i2c_set_adapdata(adapter, ctx); 419 + rc = i2c_add_adapter(adapter); 420 + if (rc) { 421 + dev_err(&pdev->dev, "Adapter registeration failed\n"); 422 + return rc; 423 + } 424 + 425 + dev_info(&pdev->dev, "Mailbox I2C Adapter registered\n"); 426 + return 0; 427 + } 428 + 429 + static int xgene_slimpro_i2c_remove(struct platform_device *pdev) 430 + { 431 + struct slimpro_i2c_dev *ctx = platform_get_drvdata(pdev); 432 + 433 + i2c_del_adapter(&ctx->adapter); 434 + 435 + mbox_free_channel(ctx->mbox_chan); 436 + 437 + return 0; 438 + } 439 + 440 + static const struct of_device_id xgene_slimpro_i2c_dt_ids[] = { 441 + {.compatible = "apm,xgene-slimpro-i2c" }, 442 + {}, 443 + }; 444 + MODULE_DEVICE_TABLE(of, xgene_slimpro_i2c_dt_ids); 445 + 446 + #ifdef CONFIG_ACPI 447 + static const struct acpi_device_id xgene_slimpro_i2c_acpi_ids[] = { 448 + {"APMC0D40", 0}, 449 + {} 450 + }; 451 + MODULE_DEVICE_TABLE(acpi, xgene_slimpro_i2c_acpi_ids); 452 + #endif 453 + 454 + static struct platform_driver xgene_slimpro_i2c_driver = { 455 + .probe = xgene_slimpro_i2c_probe, 456 + .remove = xgene_slimpro_i2c_remove, 457 + .driver = { 458 + .name = "xgene-slimpro-i2c", 459 + .of_match_table = of_match_ptr(xgene_slimpro_i2c_dt_ids), 460 + .acpi_match_table = ACPI_PTR(xgene_slimpro_i2c_acpi_ids) 461 + }, 462 + }; 463 + 464 + module_platform_driver(xgene_slimpro_i2c_driver); 465 + 466 + MODULE_DESCRIPTION("APM X-Gene SLIMpro I2C driver"); 467 + MODULE_AUTHOR("Feng Kan <fkan@apm.com>"); 468 + MODULE_AUTHOR("Hieu Le <hnle@apm.com>"); 469 + MODULE_LICENSE("GPL");