/* * CIMaX SP2/SP2HF (Atmel T90FJR) CI driver * * Copyright (C) 2014 Olli Salonen * * Heavily based on CIMax2(R) SP2 driver in conjunction with NetUp Dual * DVB-S2 CI card (cimax2) with following copyrights: * * Copyright (C) 2009 NetUP Inc. * Copyright (C) 2009 Igor M. Liplianin * Copyright (C) 2009 Abylay Ospan * * 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. */ #include "sp2_priv.h" static int sp2_read_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) { int ret; struct i2c_client *client = s->client; struct i2c_adapter *adap = client->adapter; struct i2c_msg msg[] = { { .addr = client->addr, .flags = 0, .buf = ®, .len = 1 }, { .addr = client->addr, .flags = I2C_M_RD, .buf = buf, .len = len } }; ret = i2c_transfer(adap, msg, 2); if (ret != 2) { dev_err(&client->dev, "i2c read error, reg = 0x%02x, status = %d\n", reg, ret); if (ret < 0) return ret; else return -EIO; } dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %02x\n", client->addr, reg, buf[0]); return 0; } static int sp2_write_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) { int ret; u8 buffer[35]; struct i2c_client *client = s->client; struct i2c_adapter *adap = client->adapter; struct i2c_msg msg = { .addr = client->addr, .flags = 0, .buf = &buffer[0], .len = len + 1 }; if ((len + 1) > sizeof(buffer)) { dev_err(&client->dev, "i2c wr reg=%02x: len=%d is too big!\n", reg, len); return -EINVAL; } buffer[0] = reg; memcpy(&buffer[1], buf, len); ret = i2c_transfer(adap, &msg, 1); if (ret != 1) { dev_err(&client->dev, "i2c write error, reg = 0x%02x, status = %d\n", reg, ret); if (ret < 0) return ret; else return -EIO; } dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %*ph\n", client->addr, reg, len, buf); return 0; } static int sp2_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, u8 acs, u8 read, int addr, u8 data) { struct sp2 *s = en50221->data; u8 store; int mem, ret; int (*ci_op_cam)(void*, u8, int, u8, int*) = s->ci_control; if (slot != 0) return -EINVAL; /* * change module access type between IO space and attribute memory * when needed */ if (s->module_access_type != acs) { ret = sp2_read_i2c(s, 0x00, &store, 1); if (ret) return ret; store &= ~(SP2_MOD_CTL_ACS1 | SP2_MOD_CTL_ACS0); store |= acs; ret = sp2_write_i2c(s, 0x00, &store, 1); if (ret) return ret; } s->module_access_type = acs; /* implementation of ci_op_cam is device specific */ if (ci_op_cam) { ret = ci_op_cam(s->priv, read, addr, data, &mem); } else { dev_err(&s->client->dev, "callback not defined"); return -EINVAL; } if (ret) return ret; dev_dbg(&s->client->dev, "%s: slot=%d, addr=0x%04x, %s, data=%x", (read) ? "read" : "write", slot, addr, (acs == SP2_CI_ATTR_ACS) ? "attr" : "io", (read) ? mem : data); if (read) return mem; else return 0; } int sp2_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, int slot, int addr) { return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, SP2_CI_RD, addr, 0); } int sp2_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, int slot, int addr, u8 data) { return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, SP2_CI_WR, addr, data); } int sp2_ci_read_cam_control(struct dvb_ca_en50221 *en50221, int slot, u8 addr) { return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, SP2_CI_RD, addr, 0); } int sp2_ci_write_cam_control(struct dvb_ca_en50221 *en50221, int slot, u8 addr, u8 data) { return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, SP2_CI_WR, addr, data); } int sp2_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) { struct sp2 *s = en50221->data; u8 buf; int ret; dev_dbg(&s->client->dev, "slot: %d\n", slot); if (slot != 0) return -EINVAL; /* RST on */ buf = SP2_MOD_CTL_RST; ret = sp2_write_i2c(s, 0x00, &buf, 1); if (ret) return ret; usleep_range(500, 600); /* RST off */ buf = 0x00; ret = sp2_write_i2c(s, 0x00, &buf, 1); if (ret) return ret; msleep(1000); return 0; } int sp2_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) { struct sp2 *s = en50221->data; dev_dbg(&s->client->dev, "slot:%d\n", slot); /* not implemented */ return 0; } int sp2_ci_slot_ts_enable(struct dvb_ca_en50221 *en50221, int slot) { struct sp2 *s = en50221->data; u8 buf; dev_dbg(&s->client->dev, "slot:%d\n", slot); if (slot != 0) return -EINVAL; sp2_read_i2c(s, 0x00, &buf, 1); /* disable bypass and enable TS */ buf |= (SP2_MOD_CTL_TSOEN | SP2_MOD_CTL_TSIEN); return sp2_write_i2c(s, 0, &buf, 1); } int sp2_ci_poll_slot_status(struct dvb_ca_en50221 *en50221, int slot, int open) { struct sp2 *s = en50221->data; u8 buf[2]; int ret; dev_dbg(&s->client->dev, "slot:%d open:%d\n", slot, open); /* * CAM module INSERT/REMOVE processing. Slow operation because of i2c * transfers. Throttle read to one per sec. */ if (time_after(jiffies, s->next_status_checked_time)) { ret = sp2_read_i2c(s, 0x00, buf, 1); s->next_status_checked_time = jiffies + msecs_to_jiffies(1000); if (ret) return 0; if (buf[0] & SP2_MOD_CTL_DET) s->status = DVB_CA_EN50221_POLL_CAM_PRESENT | DVB_CA_EN50221_POLL_CAM_READY; else s->status = 0; } return s->status; } static int sp2_init(struct sp2 *s) { int ret = 0; u8 buf; u8 cimax_init[34] = { 0x00, /* module A control*/ 0x00, /* auto select mask high A */ 0x00, /* auto select mask low A */ 0x00, /* auto select pattern high A */ 0x00, /* auto select pattern low A */ 0x44, /* memory access time A, 600 ns */ 0x00, /* invert input A */ 0x00, /* RFU */ 0x00, /* RFU */ 0x00, /* module B control*/ 0x00, /* auto select mask high B */ 0x00, /* auto select mask low B */ 0x00, /* auto select pattern high B */ 0x00, /* auto select pattern low B */ 0x44, /* memory access time B, 600 ns */ 0x00, /* invert input B */ 0x00, /* RFU */ 0x00, /* RFU */ 0x00, /* auto select mask high Ext */ 0x00, /* auto select mask low Ext */ 0x00, /* auto select pattern high Ext */ 0x00, /* auto select pattern low Ext */ 0x00, /* RFU */ 0x02, /* destination - module A */ 0x01, /* power control reg, VCC power on */ 0x00, /* RFU */ 0x00, /* int status read only */ 0x00, /* Interrupt Mask Register */ 0x05, /* EXTINT=active-high, INT=push-pull */ 0x00, /* USCG1 */ 0x04, /* ack active low */ 0x00, /* LOCK = 0 */ 0x22, /* unknown */ 0x00, /* synchronization? */ }; dev_dbg(&s->client->dev, "\n"); s->ca.owner = THIS_MODULE; s->ca.read_attribute_mem = sp2_ci_read_attribute_mem; s->ca.write_attribute_mem = sp2_ci_write_attribute_mem; s->ca.read_cam_control = sp2_ci_read_cam_control; s->ca.write_cam_control = sp2_ci_write_cam_control; s->ca.slot_reset = sp2_ci_slot_reset; s->ca.slot_shutdown = sp2_ci_slot_shutdown; s->ca.slot_ts_enable = sp2_ci_slot_ts_enable; s->ca.poll_slot_status = sp2_ci_poll_slot_status; s->ca.data = s; s->module_access_type = 0; /* initialize all regs */ ret = sp2_write_i2c(s, 0x00, &cimax_init[0], 34); if (ret) goto err; /* lock registers */ buf = 1; ret = sp2_write_i2c(s, 0x1f, &buf, 1); if (ret) goto err; /* power on slots */ ret = sp2_write_i2c(s, 0x18, &buf, 1); if (ret) goto err; ret = dvb_ca_en50221_init(s->dvb_adap, &s->ca, 0, 1); if (ret) goto err; return 0; err: dev_dbg(&s->client->dev, "init failed=%d\n", ret); return ret; } static int sp2_exit(struct i2c_client *client) { struct sp2 *s; dev_dbg(&client->dev, "\n"); if (!client) return 0; s = i2c_get_clientdata(client); if (!s) return 0; if (!s->ca.data) return 0; dvb_ca_en50221_release(&s->ca); return 0; } static int sp2_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct sp2_config *cfg = client->dev.platform_data; struct sp2 *s; int ret; dev_dbg(&client->dev, "\n"); s = kzalloc(sizeof(*s), GFP_KERNEL); if (!s) { ret = -ENOMEM; goto err; } s->client = client; s->dvb_adap = cfg->dvb_adap; s->priv = cfg->priv; s->ci_control = cfg->ci_control; i2c_set_clientdata(client, s); ret = sp2_init(s); if (ret) goto err; dev_info(&s->client->dev, "CIMaX SP2 successfully attached\n"); return 0; err: dev_dbg(&client->dev, "init failed=%d\n", ret); kfree(s); return ret; } static int sp2_remove(struct i2c_client *client) { struct sp2 *s = i2c_get_clientdata(client); dev_dbg(&client->dev, "\n"); sp2_exit(client); kfree(s); return 0; } static const struct i2c_device_id sp2_id[] = { {"sp2", 0}, {} }; MODULE_DEVICE_TABLE(i2c, sp2_id); static struct i2c_driver sp2_driver = { .driver = { .name = "sp2", }, .probe = sp2_probe, .remove = sp2_remove, .id_table = sp2_id, }; module_i2c_driver(sp2_driver); MODULE_DESCRIPTION("CIMaX SP2/HF CI driver"); MODULE_AUTHOR("Olli Salonen "); MODULE_LICENSE("GPL");