diff options
Diffstat (limited to 'drivers')
155 files changed, 6925 insertions, 1739 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c index 28b808c73e8..14d162952c3 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1211,6 +1211,9 @@ void device_del(struct device *dev) */ if (platform_notify_remove) platform_notify_remove(dev); + if (dev->bus) + blocking_notifier_call_chain(&dev->bus->p->bus_notifier, + BUS_NOTIFY_REMOVED_DEVICE, dev); kobject_uevent(&dev->kobj, KOBJ_REMOVE); cleanup_device_parent(dev); kobject_del(&dev->kobj); diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index 4b97baf8afa..0a54c588e43 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c @@ -210,6 +210,12 @@ enum obj_request_type { OBJ_REQUEST_NODATA, OBJ_REQUEST_BIO, OBJ_REQUEST_PAGES }; +enum obj_operation_type { + OBJ_OP_WRITE, + OBJ_OP_READ, + OBJ_OP_DISCARD, +}; + enum obj_req_flags { OBJ_REQ_DONE, /* completion flag: not done = 0, done = 1 */ OBJ_REQ_IMG_DATA, /* object usage: standalone = 0, image = 1 */ @@ -276,6 +282,7 @@ enum img_req_flags { IMG_REQ_WRITE, /* I/O direction: read = 0, write = 1 */ IMG_REQ_CHILD, /* initiator: block = 0, child image = 1 */ IMG_REQ_LAYERED, /* ENOENT handling: normal = 0, layered = 1 */ + IMG_REQ_DISCARD, /* discard: normal = 0, discard request = 1 */ }; struct rbd_img_request { @@ -785,6 +792,20 @@ static int parse_rbd_opts_token(char *c, void *private) return 0; } +static char* obj_op_name(enum obj_operation_type op_type) +{ + switch (op_type) { + case OBJ_OP_READ: + return "read"; + case OBJ_OP_WRITE: + return "write"; + case OBJ_OP_DISCARD: + return "discard"; + default: + return "???"; + } +} + /* * Get a ceph client with specific addr and configuration, if one does * not exist create it. Either way, ceph_opts is consumed by this @@ -1600,6 +1621,21 @@ static bool img_request_write_test(struct rbd_img_request *img_request) return test_bit(IMG_REQ_WRITE, &img_request->flags) != 0; } +/* + * Set the discard flag when the img_request is an discard request + */ +static void img_request_discard_set(struct rbd_img_request *img_request) +{ + set_bit(IMG_REQ_DISCARD, &img_request->flags); + smp_mb(); +} + +static bool img_request_discard_test(struct rbd_img_request *img_request) +{ + smp_mb(); + return test_bit(IMG_REQ_DISCARD, &img_request->flags) != 0; +} + static void img_request_child_set(struct rbd_img_request *img_request) { set_bit(IMG_REQ_CHILD, &img_request->flags); @@ -1636,6 +1672,17 @@ static bool img_request_layered_test(struct rbd_img_request *img_request) return test_bit(IMG_REQ_LAYERED, &img_request->flags) != 0; } +static enum obj_operation_type +rbd_img_request_op_type(struct rbd_img_request *img_request) +{ + if (img_request_write_test(img_request)) + return OBJ_OP_WRITE; + else if (img_request_discard_test(img_request)) + return OBJ_OP_DISCARD; + else + return OBJ_OP_READ; +} + static void rbd_img_obj_request_read_callback(struct rbd_obj_request *obj_request) { @@ -1722,6 +1769,21 @@ static void rbd_osd_write_callback(struct rbd_obj_request *obj_request) obj_request_done_set(obj_request); } +static void rbd_osd_discard_callback(struct rbd_obj_request *obj_request) +{ + dout("%s: obj %p result %d %llu\n", __func__, obj_request, + obj_request->result, obj_request->length); + /* + * There is no such thing as a successful short discard. Set + * it to our originally-requested length. + */ + obj_request->xferred = obj_request->length; + /* discarding a non-existent object is not a problem */ + if (obj_request->result == -ENOENT) + obj_request->result = 0; + obj_request_done_set(obj_request); +} + /* * For a simple stat call there's nothing to do. We'll do more if * this is part of a write sequence for a layered image. @@ -1773,6 +1835,11 @@ static void rbd_osd_req_callback(struct ceph_osd_request *osd_req, case CEPH_OSD_OP_STAT: rbd_osd_stat_callback(obj_request); break; + case CEPH_OSD_OP_DELETE: + case CEPH_OSD_OP_TRUNCATE: + case CEPH_OSD_OP_ZERO: + rbd_osd_discard_callback(obj_request); + break; case CEPH_OSD_OP_CALL: case CEPH_OSD_OP_NOTIFY_ACK: case CEPH_OSD_OP_WATCH: @@ -1823,7 +1890,7 @@ static void rbd_osd_req_format_write(struct rbd_obj_request *obj_request) */ static struct ceph_osd_request *rbd_osd_req_create( struct rbd_device *rbd_dev, - bool write_request, + enum obj_operation_type op_type, unsigned int num_ops, struct rbd_obj_request *obj_request) { @@ -1831,16 +1898,18 @@ static struct ceph_osd_request *rbd_osd_req_create( struct ceph_osd_client *osdc; struct ceph_osd_request *osd_req; - if (obj_request_img_data_test(obj_request)) { + if (obj_request_img_data_test(obj_request) && + (op_type == OBJ_OP_DISCARD || op_type == OBJ_OP_WRITE)) { struct rbd_img_request *img_request = obj_request->img_request; - - rbd_assert(write_request == - img_request_write_test(img_request)); - if (write_request) - snapc = img_request->snapc; + if (op_type == OBJ_OP_WRITE) { + rbd_assert(img_request_write_test(img_request)); + } else { + rbd_assert(img_request_discard_test(img_request)); + } + snapc = img_request->snapc; } - rbd_assert(num_ops == 1 || (write_request && num_ops == 2)); + rbd_assert(num_ops == 1 || ((op_type == OBJ_OP_WRITE) && num_ops == 2)); /* Allocate and initialize the request, for the num_ops ops */ @@ -1850,7 +1919,7 @@ static struct ceph_osd_request *rbd_osd_req_create( if (!osd_req) return NULL; /* ENOMEM */ - if (write_request) + if (op_type == OBJ_OP_WRITE || op_type == OBJ_OP_DISCARD) osd_req->r_flags = CEPH_OSD_FLAG_WRITE | CEPH_OSD_FLAG_ONDISK; else osd_req->r_flags = CEPH_OSD_FLAG_READ; @@ -1865,9 +1934,10 @@ static struct ceph_osd_request *rbd_osd_req_create( } /* - * Create a copyup osd request based on the information in the - * object request supplied. A copyup request has three osd ops, - * a copyup method call, a hint op, and a write op. + * Create a copyup osd request based on the information in the object + * request supplied. A copyup request has two or three osd ops, a + * copyup method call, potentially a hint op, and a write or truncate + * or zero op. */ static struct ceph_osd_request * rbd_osd_req_create_copyup(struct rbd_obj_request *obj_request) @@ -1877,18 +1947,24 @@ rbd_osd_req_create_copyup(struct rbd_obj_request *obj_request) struct rbd_device *rbd_dev; struct ceph_osd_client *osdc; struct ceph_osd_request *osd_req; + int num_osd_ops = 3; rbd_assert(obj_request_img_data_test(obj_request)); img_request = obj_request->img_request; rbd_assert(img_request); - rbd_assert(img_request_write_test(img_request)); + rbd_assert(img_request_write_test(img_request) || + img_request_discard_test(img_request)); + + if (img_request_discard_test(img_request)) + num_osd_ops = 2; - /* Allocate and initialize the request, for the three ops */ + /* Allocate and initialize the request, for all the ops */ snapc = img_request->snapc; rbd_dev = img_request->rbd_dev; osdc = &rbd_dev->rbd_client->client->osdc; - osd_req = ceph_osdc_alloc_request(osdc, snapc, 3, false, GFP_ATOMIC); + osd_req = ceph_osdc_alloc_request(osdc, snapc, num_osd_ops, + false, GFP_ATOMIC); if (!osd_req) return NULL; /* ENOMEM */ @@ -2057,7 +2133,8 @@ static bool rbd_dev_parent_get(struct rbd_device *rbd_dev) static struct rbd_img_request *rbd_img_request_create( struct rbd_device *rbd_dev, u64 offset, u64 length, - bool write_request) + enum obj_operation_type op_type, + struct ceph_snap_context *snapc) { struct rbd_img_request *img_request; @@ -2065,20 +2142,17 @@ static struct rbd_img_request *rbd_img_request_create( if (!img_request) return NULL; - if (write_request) { - down_read(&rbd_dev->header_rwsem); - ceph_get_snap_context(rbd_dev->header.snapc); - up_read(&rbd_dev->header_rwsem); - } - img_request->rq = NULL; img_request->rbd_dev = rbd_dev; img_request->offset = offset; img_request->length = length; img_request->flags = 0; - if (write_request) { + if (op_type == OBJ_OP_DISCARD) { + img_request_discard_set(img_request); + img_request->snapc = snapc; + } else if (op_type == OBJ_OP_WRITE) { img_request_write_set(img_request); - img_request->snapc = rbd_dev->header.snapc; + img_request->snapc = snapc; } else { img_request->snap_id = rbd_dev->spec->snap_id; } @@ -2093,8 +2167,7 @@ static struct rbd_img_request *rbd_img_request_create( kref_init(&img_request->kref); dout("%s: rbd_dev %p %s %llu/%llu -> img %p\n", __func__, rbd_dev, - write_request ? "write" : "read", offset, length, - img_request); + obj_op_name(op_type), offset, length, img_request); return img_request; } @@ -2118,7 +2191,8 @@ static void rbd_img_request_destroy(struct kref *kref) rbd_dev_parent_put(img_request->rbd_dev); } - if (img_request_write_test(img_request)) + if (img_request_write_test(img_request) || + img_request_discard_test(img_request)) ceph_put_snap_context(img_request->snapc); kmem_cache_free(rbd_img_request_cache, img_request); @@ -2134,8 +2208,8 @@ static struct rbd_img_request *rbd_parent_request_create( rbd_assert(obj_request->img_request); rbd_dev = obj_request->img_request->rbd_dev; - parent_request = rbd_img_request_create(rbd_dev->parent, - img_offset, length, false); + parent_request = rbd_img_request_create(rbd_dev->parent, img_offset, + length, OBJ_OP_READ, NULL); if (!parent_request) return NULL; @@ -2176,11 +2250,18 @@ static bool rbd_img_obj_end_request(struct rbd_obj_request *obj_request) result = obj_request->result; if (result) { struct rbd_device *rbd_dev = img_request->rbd_dev; + enum obj_operation_type op_type; + + if (img_request_discard_test(img_request)) + op_type = OBJ_OP_DISCARD; + else if (img_request_write_test(img_request)) + op_type = OBJ_OP_WRITE; + else + op_type = OBJ_OP_READ; rbd_warn(rbd_dev, "%s %llx at %llx (%llx)", - img_request_write_test(img_request) ? "write" : "read", - obj_request->length, obj_request->img_offset, - obj_request->offset); + obj_op_name(op_type), obj_request->length, + obj_request->img_offset, obj_request->offset); rbd_warn(rbd_dev, " result %d xferred %x", result, xferred); if (!img_request->result) @@ -2245,6 +2326,67 @@ out: } /* + * Add individual osd ops to the given ceph_osd_request and prepare + * them for submission. num_ops is the current number of + * osd operations already to the object request. + */ +static void rbd_img_obj_request_fill(struct rbd_obj_request *obj_request, + struct ceph_osd_request *osd_request, + enum obj_operation_type op_type, + unsigned int num_ops) +{ + struct rbd_img_request *img_request = obj_request->img_request; + struct rbd_device *rbd_dev = img_request->rbd_dev; + u64 object_size = rbd_obj_bytes(&rbd_dev->header); + u64 offset = obj_request->offset; + u64 length = obj_request->length; + u64 img_end; + u16 opcode; + + if (op_type == OBJ_OP_DISCARD) { + if (!offset && length == object_size && + (!img_request_layered_test(img_request) || + !obj_request_overlaps_parent(obj_request))) { + opcode = CEPH_OSD_OP_DELETE; + } else if ((offset + length == object_size)) { + opcode = CEPH_OSD_OP_TRUNCATE; + } else { + down_read(&rbd_dev->header_rwsem); + img_end = rbd_dev->header.image_size; + up_read(&rbd_dev->header_rwsem); + + if (obj_request->img_offset + length == img_end) + opcode = CEPH_OSD_OP_TRUNCATE; + else + opcode = CEPH_OSD_OP_ZERO; + } + } else if (op_type == OBJ_OP_WRITE) { + opcode = CEPH_OSD_OP_WRITE; + osd_req_op_alloc_hint_init(osd_request, num_ops, + object_size, object_size); + num_ops++; + } else { + opcode = CEPH_OSD_OP_READ; + } + + osd_req_op_extent_init(osd_request, num_ops, opcode, offset, length, + 0, 0); + if (obj_request->type == OBJ_REQUEST_BIO) + osd_req_op_extent_osd_data_bio(osd_request, num_ops, + obj_request->bio_list, length); + else if (obj_request->type == OBJ_REQUEST_PAGES) + osd_req_op_extent_osd_data_pages(osd_request, num_ops, + obj_request->pages, length, + offset & ~PAGE_MASK, false, false); + + /* Discards are also writes */ + if (op_type == OBJ_OP_WRITE || op_type == OBJ_OP_DISCARD) + rbd_osd_req_format_write(obj_request); + else + rbd_osd_req_format_read(obj_request); +} + +/* * Split up an image request into one or more object requests, each * to a different object. The "type" parameter indicates whether * "data_desc" is the pointer to the head of a list of bio @@ -2259,28 +2401,26 @@ static int rbd_img_request_fill(struct rbd_img_request *img_request, struct rbd_device *rbd_dev = img_request->rbd_dev; struct rbd_obj_request *obj_request = NULL; struct rbd_obj_request *next_obj_request; - bool write_request = img_request_write_test(img_request); struct bio *bio_list = NULL; unsigned int bio_offset = 0; struct page **pages = NULL; + enum obj_operation_type op_type; u64 img_offset; u64 resid; - u16 opcode; dout("%s: img %p type %d data_desc %p\n", __func__, img_request, (int)type, data_desc); - opcode = write_request ? CEPH_OSD_OP_WRITE : CEPH_OSD_OP_READ; img_offset = img_request->offset; resid = img_request->length; rbd_assert(resid > 0); + op_type = rbd_img_request_op_type(img_request); if (type == OBJ_REQUEST_BIO) { bio_list = data_desc; rbd_assert(img_offset == bio_list->bi_iter.bi_sector << SECTOR_SHIFT); - } else { - rbd_assert(type == OBJ_REQUEST_PAGES); + } else if (type == OBJ_REQUEST_PAGES) { pages = data_desc; } @@ -2289,7 +2429,6 @@ static int rbd_img_request_fill(struct rbd_img_request *img_request, const char *object_name; u64 offset; u64 length; - unsigned int which = 0; object_name = rbd_segment_name(rbd_dev, img_offset); if (!object_name) @@ -2321,7 +2460,7 @@ static int rbd_img_request_fill(struct rbd_img_request *img_request, GFP_ATOMIC); if (!obj_request->bio_list) goto out_unwind; - } else { + } else if (type == OBJ_REQUEST_PAGES) { unsigned int page_count; obj_request->pages = pages; @@ -2332,38 +2471,19 @@ static int rbd_img_request_fill(struct rbd_img_request *img_request, pages += page_count; } - osd_req = rbd_osd_req_create(rbd_dev, write_request, - (write_request ? 2 : 1), - obj_request); + osd_req = rbd_osd_req_create(rbd_dev, op_type, + (op_type == OBJ_OP_WRITE) ? 2 : 1, + obj_request); if (!osd_req) goto out_unwind; + obj_request->osd_req = osd_req; obj_request->callback = rbd_img_obj_callback; - rbd_img_request_get(img_request); - - if (write_request) { - osd_req_op_alloc_hint_init(osd_req, which, - rbd_obj_bytes(&rbd_dev->header), - rbd_obj_bytes(&rbd_dev->header)); - which++; - } - - osd_req_op_extent_init(osd_req, which, opcode, offset, length, - 0, 0); - if (type == OBJ_REQUEST_BIO) - osd_req_op_extent_osd_data_bio(osd_req, which, - obj_request->bio_list, length); - else - osd_req_op_extent_osd_data_pages(osd_req, which, - obj_request->pages, length, - offset & ~PAGE_MASK, false, false); + obj_request->img_offset = img_offset; - if (write_request) - rbd_osd_req_format_write(obj_request); - else - rbd_osd_req_format_read(obj_request); + rbd_img_obj_request_fill(obj_request, osd_req, op_type, 0); - obj_request->img_offset = img_offset; + rbd_img_request_get(img_request); img_offset += length; resid -= length; @@ -2386,7 +2506,8 @@ rbd_img_obj_copyup_callback(struct rbd_obj_request *obj_request) struct page **pages; u32 page_count; - rbd_assert(obj_request->type == OBJ_REQUEST_BIO); + rbd_assert(obj_request->type == OBJ_REQUEST_BIO || + obj_request->type == OBJ_REQUEST_NODATA); rbd_assert(obj_request_img_data_test(obj_request)); img_request = obj_request->img_request; rbd_assert(img_request); @@ -2424,11 +2545,10 @@ rbd_img_obj_parent_read_full_callback(struct rbd_img_request *img_request) struct ceph_osd_client *osdc; struct rbd_device *rbd_dev; struct page **pages; + enum obj_operation_type op_type; u32 page_count; int img_result; u64 parent_length; - u64 offset; - u64 length; rbd_assert(img_request_child_test(img_request)); @@ -2492,26 +2612,10 @@ rbd_img_obj_parent_read_full_callback(struct rbd_img_request *img_request) osd_req_op_cls_request_data_pages(osd_req, 0, pages, parent_length, 0, false, false); - /* Then the hint op */ + /* Add the other op(s) */ - osd_req_op_alloc_hint_init(osd_req, 1, rbd_obj_bytes(&rbd_dev->header), - rbd_obj_bytes(&rbd_dev->header)); - - /* And the original write request op */ - - offset = orig_request->offset; - length = orig_request->length; - osd_req_op_extent_init(osd_req, 2, CEPH_OSD_OP_WRITE, - offset, length, 0, 0); - if (orig_request->type == OBJ_REQUEST_BIO) - osd_req_op_extent_osd_data_bio(osd_req, 2, - orig_request->bio_list, length); - else - osd_req_op_extent_osd_data_pages(osd_req, 2, - orig_request->pages, length, - offset & ~PAGE_MASK, false, false); - - rbd_osd_req_format_write(orig_request); + op_type = rbd_img_request_op_type(orig_request->img_request); + rbd_img_obj_request_fill(orig_request, osd_req, op_type, 1); /* All set, send it off. */ @@ -2728,7 +2832,7 @@ static int rbd_img_obj_exists_submit(struct rbd_obj_request *obj_request) rbd_assert(obj_request->img_request); rbd_dev = obj_request->img_request->rbd_dev; - stat_request->osd_req = rbd_osd_req_create(rbd_dev, false, 1, + stat_request->osd_req = rbd_osd_req_create(rbd_dev, OBJ_OP_READ, 1, stat_request); if (!stat_request->osd_req) goto out; @@ -2748,11 +2852,10 @@ out: return ret; } -static int rbd_img_obj_request_submit(struct rbd_obj_request *obj_request) +static bool img_obj_request_simple(struct rbd_obj_request *obj_request) { struct rbd_img_request *img_request; struct rbd_device *rbd_dev; - bool known; rbd_assert(obj_request_img_data_test(obj_request)); @@ -2760,22 +2863,44 @@ static int rbd_img_obj_request_submit(struct rbd_obj_request *obj_request) rbd_assert(img_request); rbd_dev = img_request->rbd_dev; + /* Reads */ + if (!img_request_write_test(img_request) && + !img_request_discard_test(img_request)) + return true; + + /* Non-layered writes */ + if (!img_request_layered_test(img_request)) + return true; + + /* + * Layered writes outside of the parent overlap range don't + * share any data with the parent. + */ + if (!obj_request_overlaps_parent(obj_request)) + return true; + /* - * Only writes to layered images need special handling. - * Reads and non-layered writes are simple object requests. - * Layered writes that start beyond the end of the overlap - * with the parent have no parent data, so they too are - * simple object requests. Finally, if the target object is - * known to already exist, its parent data has already been - * copied, so a write to the object can also be handled as a - * simple object request. + * Entire-object layered writes - we will overwrite whatever + * parent data there is anyway. */ - if (!img_request_write_test(img_request) || - !img_request_layered_test(img_request) || - !obj_request_overlaps_parent(obj_request) || - ((known = obj_request_known_test(obj_request)) && - obj_request_exists_test(obj_request))) { + if (!obj_request->offset && + obj_request->length == rbd_obj_bytes(&rbd_dev->header)) + return true; + + /* + * If the object is known to already exist, its parent data has + * already been copied. + */ + if (obj_request_known_test(obj_request) && + obj_request_exists_test(obj_request)) + return true; + + return false; +} +static int rbd_img_obj_request_submit(struct rbd_obj_request *obj_request) +{ + if (img_obj_request_simple(obj_request)) { struct rbd_device *rbd_dev; struct ceph_osd_client *osdc; @@ -2791,7 +2916,7 @@ static int rbd_img_obj_request_submit(struct rbd_obj_request *obj_request) * start by reading the data for the full target object from * the parent so we can use it for a copyup to the target. */ - if (known) + if (obj_request_known_test(obj_request)) return rbd_img_obj_parent_read_full(obj_request); /* We don't know whether the target exists. Go find out. */ @@ -2932,7 +3057,7 @@ static int rbd_obj_notify_ack_sync(struct rbd_device *rbd_dev, u64 notify_id) return -ENOMEM; ret = -ENOMEM; - obj_request->osd_req = rbd_osd_req_create(rbd_dev, false, 1, + obj_request->osd_req = rbd_osd_req_create(rbd_dev, OBJ_OP_READ, 1, obj_request); if (!obj_request->osd_req) goto out; @@ -2995,7 +3120,7 @@ static struct rbd_obj_request *rbd_obj_watch_request_helper( if (!obj_request) return ERR_PTR(-ENOMEM); - obj_request->osd_req = rbd_osd_req_create(rbd_dev, true, 1, + obj_request->osd_req = rbd_osd_req_create(rbd_dev, OBJ_OP_WRITE, 1, obj_request); if (!obj_request->osd_req) { ret = -ENOMEM; @@ -3133,7 +3258,7 @@ static int rbd_obj_method_sync(struct rbd_device *rbd_dev, obj_request->pages = pages; obj_request->page_count = page_count; - obj_request->osd_req = rbd_osd_req_create(rbd_dev, false, 1, + obj_request->osd_req = rbd_osd_req_create(rbd_dev, OBJ_OP_READ, 1, obj_request); if (!obj_request->osd_req) goto out; @@ -3183,11 +3308,20 @@ out: static void rbd_handle_request(struct rbd_device *rbd_dev, struct request *rq) { struct rbd_img_request *img_request; + struct ceph_snap_context *snapc = NULL; u64 offset = (u64)blk_rq_pos(rq) << SECTOR_SHIFT; u64 length = blk_rq_bytes(rq); - bool wr = rq_data_dir(rq) == WRITE; + enum obj_operation_type op_type; + u64 mapping_size; int result; + if (rq->cmd_flags & REQ_DISCARD) + op_type = OBJ_OP_DISCARD; + else if (rq->cmd_flags & REQ_WRITE) + op_type = OBJ_OP_WRITE; + else + op_type = OBJ_OP_READ; + /* Ignore/skip any zero-length requests */ if (!length) { @@ -3196,9 +3330,9 @@ static void rbd_handle_request(struct rbd_device *rbd_dev, struct request *rq) goto err_rq; } - /* Disallow writes to a read-only device */ + /* Only reads are allowed to a read-only device */ - if (wr) { + if (op_type != OBJ_OP_READ) { if (rbd_dev->mapping.read_only) { result = -EROFS; goto err_rq; @@ -3226,21 +3360,35 @@ static void rbd_handle_request(struct rbd_device *rbd_dev, struct request *rq) goto err_rq; /* Shouldn't happen */ } - if (offset + length > rbd_dev->mapping.size) { + down_read(&rbd_dev->header_rwsem); + mapping_size = rbd_dev->mapping.size; + if (op_type != OBJ_OP_READ) { + snapc = rbd_dev->header.snapc; + ceph_get_snap_context(snapc); + } + up_read(&rbd_dev->header_rwsem); + + if (offset + length > mapping_size) { rbd_warn(rbd_dev, "beyond EOD (%llu~%llu > %llu)", offset, - length, rbd_dev->mapping.size); + length, mapping_size); result = -EIO; goto err_rq; } - img_request = rbd_img_request_create(rbd_dev, offset, length, wr); + img_request = rbd_img_request_create(rbd_dev, offset, length, op_type, + snapc); if (!img_request) { result = -ENOMEM; goto err_rq; } img_request->rq = rq; - result = rbd_img_request_fill(img_request, OBJ_REQUEST_BIO, rq->bio); + if (op_type == OBJ_OP_DISCARD) + result = rbd_img_request_fill(img_request, OBJ_REQUEST_NODATA, + NULL); + else + result = rbd_img_request_fill(img_request, OBJ_REQUEST_BIO, + rq->bio); if (result) goto err_img_request; @@ -3255,7 +3403,9 @@ err_img_request: err_rq: if (result) rbd_warn(rbd_dev, "%s %llx at %llx result %d", - wr ? "write" : "read", length, offset, result); + obj_op_name(op_type), length, offset, result); + if (snapc) + ceph_put_snap_context(snapc); blk_end_request_all(rq, result); } @@ -3393,7 +3543,7 @@ static int rbd_obj_read_sync(struct rbd_device *rbd_dev, obj_request->pages = pages; obj_request->page_count = page_count; - obj_request->osd_req = rbd_osd_req_create(rbd_dev, false, 1, + obj_request->osd_req = rbd_osd_req_create(rbd_dev, OBJ_OP_READ, 1, obj_request); if (!obj_request->osd_req) goto out; @@ -3610,6 +3760,13 @@ static int rbd_init_disk(struct rbd_device *rbd_dev) blk_queue_io_min(q, segment_size); blk_queue_io_opt(q, segment_size); + /* enable the discard support */ + queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q); + q->limits.discard_granularity = segment_size; + q->limits.discard_alignment = segment_size; + q->limits.max_discard_sectors = segment_size / SECTOR_SIZE; + q->limits.discard_zeroes_data = 1; + blk_queue_merge_bvec(q, rbd_merge_bvec); disk->queue = q; @@ -4924,7 +5081,7 @@ static int rbd_dev_image_id(struct rbd_device *rbd_dev) ret = image_id ? 0 : -ENOMEM; if (!ret) rbd_dev->image_format = 1; - } else if (ret > sizeof (__le32)) { + } else if (ret >= 0) { void *p = response; image_id = ceph_extract_encoded_string(&p, p + ret, @@ -4932,8 +5089,6 @@ static int rbd_dev_image_id(struct rbd_device *rbd_dev) ret = PTR_ERR_OR_ZERO(image_id); if (!ret) rbd_dev->image_format = 2; - } else { - ret = -EINVAL; } if (!ret) { @@ -5087,7 +5242,8 @@ static int rbd_dev_device_setup(struct rbd_device *rbd_dev) set_capacity(rbd_dev->disk, rbd_dev->mapping.size / SECTOR_SIZE); set_disk_ro(rbd_dev->disk, rbd_dev->mapping.read_only); - rbd_dev->rq_wq = alloc_workqueue("%s", 0, 0, rbd_dev->disk->disk_name); + rbd_dev->rq_wq = alloc_workqueue("%s", WQ_MEM_RECLAIM, 0, + rbd_dev->disk->disk_name); if (!rbd_dev->rq_wq) { ret = -ENOMEM; goto err_out_mapping; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 84e0590e31d..455fd17d938 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -32,12 +32,23 @@ config COMMON_CLK_WM831X source "drivers/clk/versatile/Kconfig" +config COMMON_CLK_MAX_GEN + bool + config COMMON_CLK_MAX77686 tristate "Clock driver for Maxim 77686 MFD" depends on MFD_MAX77686 + select COMMON_CLK_MAX_GEN ---help--- This driver supports Maxim 77686 crystal oscillator clock. +config COMMON_CLK_MAX77802 + tristate "Clock driver for Maxim 77802 PMIC" + depends on MFD_MAX77686 + select COMMON_CLK_MAX_GEN + ---help--- + This driver supports Maxim 77802 crystal oscillator clock. + config COMMON_CLK_RK808 tristate "Clock driver for RK808" depends on MFD_RK808 @@ -118,6 +129,11 @@ config COMMON_CLK_PALMAS This driver supports TI Palmas devices 32KHz output KG and KG_AUDIO using common clock framework. +config COMMON_CLK_PXA + def_bool COMMON_CLK && ARCH_PXA + ---help--- + Sypport for the Marvell PXA SoC. + source "drivers/clk/qcom/Kconfig" endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 99f53d5f876..d5fba5bc6e1 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-gate.o obj-$(CONFIG_COMMON_CLK) += clk-mux.o obj-$(CONFIG_COMMON_CLK) += clk-composite.o obj-$(CONFIG_COMMON_CLK) += clk-fractional-divider.o +obj-$(CONFIG_COMMON_CLK) += clk-gpio-gate.o ifeq ($(CONFIG_OF), y) obj-$(CONFIG_COMMON_CLK) += clk-conf.o endif @@ -22,7 +23,9 @@ obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o obj-$(CONFIG_MACH_LOONGSON1) += clk-ls1x.o +obj-$(CONFIG_COMMON_CLK_MAX_GEN) += clk-max-gen.o obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o +obj-$(CONFIG_COMMON_CLK_MAX77802) += clk-max77802.o obj-$(CONFIG_ARCH_MOXART) += clk-moxart.o obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o @@ -49,6 +52,7 @@ obj-$(CONFIG_ARCH_MMP) += mmp/ endif obj-$(CONFIG_PLAT_ORION) += mvebu/ obj-$(CONFIG_ARCH_MXS) += mxs/ +obj-$(CONFIG_COMMON_CLK_PXA) += pxa/ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/ diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c index cf6ed023504..6ec79dbc084 100644 --- a/drivers/clk/at91/clk-pll.c +++ b/drivers/clk/at91/clk-pll.c @@ -15,6 +15,7 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/io.h> +#include <linux/kernel.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/interrupt.h> @@ -29,9 +30,12 @@ #define PLL_DIV(reg) ((reg) & PLL_DIV_MASK) #define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \ (layout)->mul_mask) +#define PLL_MUL_MIN 2 +#define PLL_MUL_MASK(layout) ((layout)->mul_mask) +#define PLL_MUL_MAX(layout) (PLL_MUL_MASK(layout) + 1) #define PLL_ICPR_SHIFT(id) ((id) * 16) #define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id)) -#define PLL_MAX_COUNT 0x3ff +#define PLL_MAX_COUNT 0x3f #define PLL_COUNT_SHIFT 8 #define PLL_OUT_SHIFT 14 #define PLL_MAX_ID 1 @@ -147,115 +151,113 @@ static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_pll *pll = to_clk_pll(hw); - const struct clk_pll_layout *layout = pll->layout; - struct at91_pmc *pmc = pll->pmc; - int offset = PLL_REG(pll->id); - u32 tmp = pmc_read(pmc, offset) & layout->pllr_mask; - u8 div = PLL_DIV(tmp); - u16 mul = PLL_MUL(tmp, layout); - if (!div || !mul) + + if (!pll->div || !pll->mul) return 0; - return (parent_rate * (mul + 1)) / div; + return (parent_rate / pll->div) * (pll->mul + 1); } static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate, unsigned long parent_rate, u32 *div, u32 *mul, u32 *index) { - unsigned long maxrate; - unsigned long minrate; - unsigned long divrate; - unsigned long bestdiv = 1; - unsigned long bestmul; - unsigned long tmpdiv; - unsigned long roundup; - unsigned long rounddown; - unsigned long remainder; - unsigned long bestremainder; - unsigned long maxmul; - unsigned long maxdiv; - unsigned long mindiv; - int i = 0; const struct clk_pll_layout *layout = pll->layout; const struct clk_pll_characteristics *characteristics = pll->characteristics; + unsigned long bestremainder = ULONG_MAX; + unsigned long maxdiv, mindiv, tmpdiv; + long bestrate = -ERANGE; + unsigned long bestdiv; + unsigned long bestmul; + int i = 0; - /* Minimum divider = 1 */ - /* Maximum multiplier = max_mul */ - maxmul = layout->mul_mask + 1; - maxrate = (parent_rate * maxmul) / 1; - - /* Maximum divider = max_div */ - /* Minimum multiplier = 2 */ - maxdiv = PLL_DIV_MAX; - minrate = (parent_rate * 2) / maxdiv; - + /* Check if parent_rate is a valid input rate */ if (parent_rate < characteristics->input.min || - parent_rate < characteristics->input.max) - return -ERANGE; - - if (parent_rate < minrate || parent_rate > maxrate) + parent_rate > characteristics->input.max) return -ERANGE; - for (i = 0; i < characteristics->num_output; i++) { - if (parent_rate >= characteristics->output[i].min && - parent_rate <= characteristics->output[i].max) - break; - } - - if (i >= characteristics->num_output) - return -ERANGE; - - bestmul = rate / parent_rate; - rounddown = parent_rate % rate; - roundup = rate - rounddown; - bestremainder = roundup < rounddown ? roundup : rounddown; - - if (!bestremainder) { - if (div) - *div = bestdiv; - if (mul) - *mul = bestmul; - if (index) - *index = i; - return rate; - } - - maxdiv = 255 / (bestmul + 1); - if (parent_rate / maxdiv < characteristics->input.min) - maxdiv = parent_rate / characteristics->input.min; - mindiv = parent_rate / characteristics->input.max; - if (parent_rate % characteristics->input.max) - mindiv++; - - for (tmpdiv = mindiv; tmpdiv < maxdiv; tmpdiv++) { - divrate = parent_rate / tmpdiv; - - rounddown = rate % divrate; - roundup = divrate - rounddown; - remainder = roundup < rounddown ? roundup : rounddown; - + /* + * Calculate minimum divider based on the minimum multiplier, the + * parent_rate and the requested rate. + * Should always be 2 according to the input and output characteristics + * of the PLL blocks. + */ + mindiv = (parent_rate * PLL_MUL_MIN) / rate; + if (!mindiv) + mindiv = 1; + + /* + * Calculate the maximum divider which is limited by PLL register + * layout (limited by the MUL or DIV field size). + */ + maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX(layout), rate); + if (maxdiv > PLL_DIV_MAX) + maxdiv = PLL_DIV_MAX; + + /* + * Iterate over the acceptable divider values to find the best + * divider/multiplier pair (the one that generates the closest + * rate to the requested one). + */ + for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) { + unsigned long remainder; + unsigned long tmprate; + unsigned long tmpmul; + + /* + * Calculate the multiplier associated with the current + * divider that provide the closest rate to the requested one. + */ + tmpmul = DIV_ROUND_CLOSEST(rate, parent_rate / tmpdiv); + tmprate = (parent_rate / tmpdiv) * tmpmul; + if (tmprate > rate) + remainder = tmprate - rate; + else + remainder = rate - tmprate; + + /* + * Compare the remainder with the best remainder found until + * now and elect a new best multiplier/divider pair if the + * current remainder is smaller than the best one. + */ if (remainder < bestremainder) { bestremainder = remainder; - bestmul = rate / divrate; bestdiv = tmpdiv; + bestmul = tmpmul; + bestrate = tmprate; } + /* + * We've found a perfect match! + * Stop searching now and use this multiplier/divider pair. + */ if (!remainder) break; } - rate = (parent_rate / bestdiv) * bestmul; + /* We haven't found any multiplier/divider pair => return -ERANGE */ + if (bestrate < 0) + return bestrate; + + /* Check if bestrate is a valid output rate */ + for (i = 0; i < characteristics->num_output; i++) { + if (bestrate >= characteristics->output[i].min && + bestrate <= characteristics->output[i].max) + break; + } + + if (i >= characteristics->num_output) + return -ERANGE; if (div) *div = bestdiv; if (mul) - *mul = bestmul; + *mul = bestmul - 1; if (index) *index = i; - return rate; + return bestrate; } static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c index 7d1d26a4bd0..24b5b020753 100644 --- a/drivers/clk/at91/clk-usb.c +++ b/drivers/clk/at91/clk-usb.c @@ -238,16 +238,22 @@ static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw); + struct clk *parent = __clk_get_parent(hw->clk); unsigned long bestrate = 0; int bestdiff = -1; unsigned long tmprate; int tmpdiff; int i = 0; - for (i = 0; i < 4; i++) { + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) { + unsigned long tmp_parent_rate; + if (!usb->divisors[i]) continue; - tmprate = *parent_rate / usb->divisors[i]; + + tmp_parent_rate = rate * usb->divisors[i]; + tmp_parent_rate = __clk_round_rate(parent, tmp_parent_rate); + tmprate = tmp_parent_rate / usb->divisors[i]; if (tmprate < rate) tmpdiff = rate - tmprate; else @@ -256,6 +262,7 @@ static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate, if (bestdiff < 0 || bestdiff > tmpdiff) { bestrate = tmprate; bestdiff = tmpdiff; + *parent_rate = tmp_parent_rate; } if (!bestdiff) @@ -272,10 +279,13 @@ static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate, int i; struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw); struct at91_pmc *pmc = usb->pmc; - unsigned long div = parent_rate / rate; + unsigned long div; - if (parent_rate % rate) + if (!rate || parent_rate % rate) return -EINVAL; + + div = parent_rate / rate; + for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) { if (usb->divisors[i] == div) { tmp = pmc_read(pmc, AT91_CKGR_PLLBR) & @@ -311,7 +321,7 @@ at91rm9200_clk_register_usb(struct at91_pmc *pmc, const char *name, init.ops = &at91rm9200_usb_ops; init.parent_names = &parent_name; init.num_parents = 1; - init.flags = 0; + init.flags = CLK_SET_RATE_PARENT; usb->hw.init = &init; usb->pmc = pmc; diff --git a/drivers/clk/clk-axi-clkgen.c b/drivers/clk/clk-axi-clkgen.c index 1127ee46b80..e619285c6de 100644 --- a/drivers/clk/clk-axi-clkgen.c +++ b/drivers/clk/clk-axi-clkgen.c @@ -544,7 +544,6 @@ static int axi_clkgen_remove(struct platform_device *pdev) static struct platform_driver axi_clkgen_driver = { .driver = { .name = "adi-axi-clkgen", - .owner = THIS_MODULE, .of_match_table = axi_clkgen_ids, }, .probe = axi_clkgen_probe, diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c index ede685ca0d2..82a59d0086c 100644 --- a/drivers/clk/clk-fractional-divider.c +++ b/drivers/clk/clk-fractional-divider.c @@ -36,7 +36,7 @@ static unsigned long clk_fd_recalc_rate(struct clk_hw *hw, m = (val & fd->mmask) >> fd->mshift; n = (val & fd->nmask) >> fd->nshift; - ret = parent_rate * m; + ret = (u64)parent_rate * m; do_div(ret, n); return ret; diff --git a/drivers/clk/clk-gate.c b/drivers/clk/clk-gate.c index 4a58c55255b..51fd87fb7ba 100644 --- a/drivers/clk/clk-gate.c +++ b/drivers/clk/clk-gate.c @@ -45,7 +45,7 @@ static void clk_gate_endisable(struct clk_hw *hw, int enable) { struct clk_gate *gate = to_clk_gate(hw); int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0; - unsigned long flags = 0; + unsigned long uninitialized_var(flags); u32 reg; set ^= enable; diff --git a/drivers/clk/clk-gpio-gate.c b/drivers/clk/clk-gpio-gate.c new file mode 100644 index 00000000000..08e43224fd5 --- /dev/null +++ b/drivers/clk/clk-gpio-gate.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2013 - 2014 Texas Instruments Incorporated - http://www.ti.com + * Author: Jyri Sarha <jsarha@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Gpio gated clock implementation + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/of_gpio.h> +#include <linux/err.h> +#include <linux/device.h> + +/** + * DOC: basic gpio gated clock which can be enabled and disabled + * with gpio output + * Traits of this clock: + * prepare - clk_(un)prepare only ensures parent is (un)prepared + * enable - clk_enable and clk_disable are functional & control gpio + * rate - inherits rate from parent. No clk_set_rate support + * parent - fixed parent. No clk_set_parent support + */ + +#define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw) + +static int clk_gpio_gate_enable(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + + gpiod_set_value(clk->gpiod, 1); + + return 0; +} + +static void clk_gpio_gate_disable(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + + gpiod_set_value(clk->gpiod, 0); +} + +static int clk_gpio_gate_is_enabled(struct clk_hw *hw) +{ + struct clk_gpio *clk = to_clk_gpio(hw); + + return gpiod_get_value(clk->gpiod); +} + +const struct clk_ops clk_gpio_gate_ops = { + .enable = clk_gpio_gate_enable, + .disable = clk_gpio_gate_disable, + .is_enabled = clk_gpio_gate_is_enabled, +}; +EXPORT_SYMBOL_GPL(clk_gpio_gate_ops); + +/** + * clk_register_gpio - register a gpip clock with the clock framework + * @dev: device that is registering this clock + * @name: name of this clock + * @parent_name: name of this clock's parent + * @gpiod: gpio descriptor to gate this clock + */ +struct clk *clk_register_gpio_gate(struct device *dev, const char *name, + const char *parent_name, struct gpio_desc *gpiod, + unsigned long flags) +{ + struct clk_gpio *clk_gpio = NULL; + struct clk *clk = ERR_PTR(-EINVAL); + struct clk_init_data init = { NULL }; + unsigned long gpio_flags; + int err; + + if (gpiod_is_active_low(gpiod)) + gpio_flags = GPIOF_OUT_INIT_HIGH; + else + gpio_flags = GPIOF_OUT_INIT_LOW; + + if (dev) + err = devm_gpio_request_one(dev, desc_to_gpio(gpiod), + gpio_flags, name); + else + err = gpio_request_one(desc_to_gpio(gpiod), gpio_flags, name); + + if (err) { + pr_err("%s: %s: Error requesting clock control gpio %u\n", + __func__, name, desc_to_gpio(gpiod)); + return ERR_PTR(err); + } + + if (dev) + clk_gpio = devm_kzalloc(dev, sizeof(struct clk_gpio), + GFP_KERNEL); + else + clk_gpio = kzalloc(sizeof(struct clk_gpio), GFP_KERNEL); + + if (!clk_gpio) { + clk = ERR_PTR(-ENOMEM); + goto clk_register_gpio_gate_err; + } + + init.name = name; + init.ops = &clk_gpio_gate_ops; + init.flags = flags | CLK_IS_BASIC; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + clk_gpio->gpiod = gpiod; + clk_gpio->hw.init = &init; + + clk = clk_register(dev, &clk_gpio->hw); + + if (!IS_ERR(clk)) + return clk; + + if (!dev) + kfree(clk_gpio); + +clk_register_gpio_gate_err: + gpiod_put(gpiod); + + return clk; +} +EXPORT_SYMBOL_GPL(clk_register_gpio_gate); + +#ifdef CONFIG_OF +/** + * The clk_register_gpio_gate has to be delayed, because the EPROBE_DEFER + * can not be handled properly at of_clk_init() call time. + */ + +struct clk_gpio_gate_delayed_register_data { + struct device_node *node; + struct mutex lock; + struct clk *clk; +}; + +static struct clk *of_clk_gpio_gate_delayed_register_get( + struct of_phandle_args *clkspec, + void *_data) +{ + struct clk_gpio_gate_delayed_register_data *data = _data; + struct clk *clk; + const char *clk_name = data->node->name; + const char *parent_name; + struct gpio_desc *gpiod; + int gpio; + + mutex_lock(&data->lock); + + if (data->clk) { + mutex_unlock(&data->lock); + return data->clk; + } + + gpio = of_get_named_gpio_flags(data->node, "enable-gpios", 0, NULL); + if (gpio < 0) { + mutex_unlock(&data->lock); + if (gpio != -EPROBE_DEFER) + pr_err("%s: %s: Can't get 'enable-gpios' DT property\n", + __func__, clk_name); + return ERR_PTR(gpio); + } + gpiod = gpio_to_desc(gpio); + + parent_name = of_clk_get_parent_name(data->node, 0); + + clk = clk_register_gpio_gate(NULL, clk_name, parent_name, gpiod, 0); + if (IS_ERR(clk)) { + mutex_unlock(&data->lock); + return clk; + } + + data->clk = clk; + mutex_unlock(&data->lock); + + return clk; +} + +/** + * of_gpio_gate_clk_setup() - Setup function for gpio controlled clock + */ +void __init of_gpio_gate_clk_setup(struct device_node *node) +{ + struct clk_gpio_gate_delayed_register_data *data; + + data = kzalloc(sizeof(struct clk_gpio_gate_delayed_register_data), + GFP_KERNEL); + if (!data) + return; + + data->node = node; + mutex_init(&data->lock); + + of_clk_add_provider(node, of_clk_gpio_gate_delayed_register_get, data); +} +EXPORT_SYMBOL_GPL(of_gpio_gate_clk_setup); +CLK_OF_DECLARE(gpio_gate_clk, "gpio-gate-clock", of_gpio_gate_clk_setup); +#endif diff --git a/drivers/clk/clk-max-gen.c b/drivers/clk/clk-max-gen.c new file mode 100644 index 00000000000..6505049d50f --- /dev/null +++ b/drivers/clk/clk-max-gen.c @@ -0,0 +1,192 @@ +/* + * clk-max-gen.c - Generic clock driver for Maxim PMICs clocks + * + * Copyright (C) 2014 Google, Inc + * + * Copyright (C) 2012 Samsung Electornics + * Jonghwa Lee <jonghwa3.lee@samsung.com> + * + * 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. + * + * This driver is based on clk-max77686.c + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/clk-provider.h> +#include <linux/mutex.h> +#include <linux/clkdev.h> +#include <linux/of.h> +#include <linux/export.h> + +struct max_gen_clk { + struct regmap *regmap; + u32 mask; + u32 reg; + struct clk_hw hw; +}; + +static struct max_gen_clk *to_max_gen_clk(struct clk_hw *hw) +{ + return container_of(hw, struct max_gen_clk, hw); +} + +static int max_gen_clk_prepare(struct clk_hw *hw) +{ + struct max_gen_clk *max_gen = to_max_gen_clk(hw); + + return regmap_update_bits(max_gen->regmap, max_gen->reg, + max_gen->mask, max_gen->mask); +} + +static void max_gen_clk_unprepare(struct clk_hw *hw) +{ + struct max_gen_clk *max_gen = to_max_gen_clk(hw); + + regmap_update_bits(max_gen->regmap, max_gen->reg, + max_gen->mask, ~max_gen->mask); +} + +static int max_gen_clk_is_prepared(struct clk_hw *hw) +{ + struct max_gen_clk *max_gen = to_max_gen_clk(hw); + int ret; + u32 val; + + ret = regmap_read(max_gen->regmap, max_gen->reg, &val); + + if (ret < 0) + return -EINVAL; + + return val & max_gen->mask; +} + +static unsigned long max_gen_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 32768; +} + +struct clk_ops max_gen_clk_ops = { + .prepare = max_gen_clk_prepare, + .unprepare = max_gen_clk_unprepare, + .is_prepared = max_gen_clk_is_prepared, + .recalc_rate = max_gen_recalc_rate, +}; +EXPORT_SYMBOL_GPL(max_gen_clk_ops); + +static struct clk *max_gen_clk_register(struct device *dev, + struct max_gen_clk *max_gen) +{ + struct clk *clk; + struct clk_hw *hw = &max_gen->hw; + int ret; + + clk = devm_clk_register(dev, hw); + if (IS_ERR(clk)) + return clk; + + ret = clk_register_clkdev(clk, hw->init->name, NULL); + + if (ret) + return ERR_PTR(ret); + + return clk; +} + +int max_gen_clk_probe(struct platform_device *pdev, struct regmap *regmap, + u32 reg, struct clk_init_data *clks_init, int num_init) +{ + int i, ret; + struct max_gen_clk *max_gen_clks; + struct clk **clocks; + struct device *dev = pdev->dev.parent; + const char *clk_name; + struct clk_init_data *init; + + clocks = devm_kzalloc(dev, sizeof(struct clk *) * num_init, GFP_KERNEL); + if (!clocks) + return -ENOMEM; + + max_gen_clks = devm_kzalloc(dev, sizeof(struct max_gen_clk) + * num_init, GFP_KERNEL); + if (!max_gen_clks) + return -ENOMEM; + + for (i = 0; i < num_init; i++) { + max_gen_clks[i].regmap = regmap; + max_gen_clks[i].mask = 1 << i; + max_gen_clks[i].reg = reg; + + init = devm_kzalloc(dev, sizeof(*init), GFP_KERNEL); + if (!init) + return -ENOMEM; + + if (dev->of_node && + !of_property_read_string_index(dev->of_node, + "clock-output-names", + i, &clk_name)) + init->name = clk_name; + else + init->name = clks_init[i].name; + + init->ops = clks_init[i].ops; + init->flags = clks_init[i].flags; + + max_gen_clks[i].hw.init = init; + + clocks[i] = max_gen_clk_register(dev, &max_gen_clks[i]); + if (IS_ERR(clocks[i])) { + ret = PTR_ERR(clocks[i]); + dev_err(dev, "failed to register %s\n", + max_gen_clks[i].hw.init->name); + return ret; + } + } + + platform_set_drvdata(pdev, clocks); + + if (dev->of_node) { + struct clk_onecell_data *of_data; + + of_data = devm_kzalloc(dev, sizeof(*of_data), GFP_KERNEL); + if (!of_data) + return -ENOMEM; + + of_data->clks = clocks; + of_data->clk_num = num_init; + ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + of_data); + + if (ret) { + dev_err(dev, "failed to register OF clock provider\n"); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(max_gen_clk_probe); + +int max_gen_clk_remove(struct platform_device *pdev, int num_init) +{ + struct device *dev = pdev->dev.parent; + + if (dev->of_node) + of_clk_del_provider(dev->of_node); + + return 0; +} +EXPORT_SYMBOL_GPL(max_gen_clk_remove); diff --git a/drivers/clk/clk-max-gen.h b/drivers/clk/clk-max-gen.h new file mode 100644 index 00000000000..997e86fc3f4 --- /dev/null +++ b/drivers/clk/clk-max-gen.h @@ -0,0 +1,32 @@ +/* + * clk-max-gen.h - Generic clock driver for Maxim PMICs clocks + * + * Copyright (C) 2014 Google, Inc + * + * 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. + * + */ + +#ifndef __CLK_MAX_GEN_H__ +#define __CLK_MAX_GEN_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clkdev.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> + +int max_gen_clk_probe(struct platform_device *pdev, struct regmap *regmap, + u32 reg, struct clk_init_data *clks_init, int num_init); +int max_gen_clk_remove(struct platform_device *pdev, int num_init); +extern struct clk_ops max_gen_clk_ops; + +#endif /* __CLK_MAX_GEN_H__ */ diff --git a/drivers/clk/clk-max77686.c b/drivers/clk/clk-max77686.c index 3d7e8dd8fd5..86cdb3a2862 100644 --- a/drivers/clk/clk-max77686.c +++ b/drivers/clk/clk-max77686.c @@ -30,193 +30,38 @@ #include <linux/mutex.h> #include <linux/clkdev.h> -enum { - MAX77686_CLK_AP = 0, - MAX77686_CLK_CP, - MAX77686_CLK_PMIC, - MAX77686_CLKS_NUM, -}; - -struct max77686_clk { - struct max77686_dev *iodev; - u32 mask; - struct clk_hw hw; - struct clk_lookup *lookup; -}; - -static struct max77686_clk *to_max77686_clk(struct clk_hw *hw) -{ - return container_of(hw, struct max77686_clk, hw); -} - -static int max77686_clk_prepare(struct clk_hw *hw) -{ - struct max77686_clk *max77686 = to_max77686_clk(hw); - - return regmap_update_bits(max77686->iodev->regmap, - MAX77686_REG_32KHZ, max77686->mask, - max77686->mask); -} - -static void max77686_clk_unprepare(struct clk_hw *hw) -{ - struct max77686_clk *max77686 = to_max77686_clk(hw); - - regmap_update_bits(max77686->iodev->regmap, - MAX77686_REG_32KHZ, max77686->mask, ~max77686->mask); -} - -static int max77686_clk_is_prepared(struct clk_hw *hw) -{ - struct max77686_clk *max77686 = to_max77686_clk(hw); - int ret; - u32 val; - - ret = regmap_read(max77686->iodev->regmap, - MAX77686_REG_32KHZ, &val); - - if (ret < 0) - return -EINVAL; - - return val & max77686->mask; -} - -static unsigned long max77686_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - return 32768; -} - -static struct clk_ops max77686_clk_ops = { - .prepare = max77686_clk_prepare, - .unprepare = max77686_clk_unprepare, - .is_prepared = max77686_clk_is_prepared, - .recalc_rate = max77686_recalc_rate, -}; +#include <dt-bindings/clock/maxim,max77686.h> +#include "clk-max-gen.h" static struct clk_init_data max77686_clks_init[MAX77686_CLKS_NUM] = { [MAX77686_CLK_AP] = { .name = "32khz_ap", - .ops = &max77686_clk_ops, + .ops = &max_gen_clk_ops, .flags = CLK_IS_ROOT, }, [MAX77686_CLK_CP] = { .name = "32khz_cp", - .ops = &max77686_clk_ops, + .ops = &max_gen_clk_ops, .flags = CLK_IS_ROOT, }, [MAX77686_CLK_PMIC] = { .name = "32khz_pmic", - .ops = &max77686_clk_ops, + .ops = &max_gen_clk_ops, .flags = CLK_IS_ROOT, }, }; -static struct clk *max77686_clk_register(struct device *dev, - struct max77686_clk *max77686) -{ - struct clk *clk; - struct clk_hw *hw = &max77686->hw; - - clk = clk_register(dev, hw); - if (IS_ERR(clk)) - return clk; - - max77686->lookup = kzalloc(sizeof(struct clk_lookup), GFP_KERNEL); - if (!max77686->lookup) - return ERR_PTR(-ENOMEM); - - max77686->lookup->con_id = hw->init->name; - max77686->lookup->clk = clk; - - clkdev_add(max77686->lookup); - - return clk; -} - static int max77686_clk_probe(struct platform_device *pdev) { struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct max77686_clk *max77686_clks[MAX77686_CLKS_NUM]; - struct clk **clocks; - int i, ret; - - clocks = devm_kzalloc(&pdev->dev, sizeof(struct clk *) - * MAX77686_CLKS_NUM, GFP_KERNEL); - if (!clocks) - return -ENOMEM; - - for (i = 0; i < MAX77686_CLKS_NUM; i++) { - max77686_clks[i] = devm_kzalloc(&pdev->dev, - sizeof(struct max77686_clk), GFP_KERNEL); - if (!max77686_clks[i]) - return -ENOMEM; - } - - for (i = 0; i < MAX77686_CLKS_NUM; i++) { - max77686_clks[i]->iodev = iodev; - max77686_clks[i]->mask = 1 << i; - max77686_clks[i]->hw.init = &max77686_clks_init[i]; - - clocks[i] = max77686_clk_register(&pdev->dev, max77686_clks[i]); - if (IS_ERR(clocks[i])) { - ret = PTR_ERR(clocks[i]); - dev_err(&pdev->dev, "failed to register %s\n", - max77686_clks[i]->hw.init->name); - goto err_clocks; - } - } - - platform_set_drvdata(pdev, clocks); - - if (iodev->dev->of_node) { - struct clk_onecell_data *of_data; - of_data = devm_kzalloc(&pdev->dev, - sizeof(*of_data), GFP_KERNEL); - if (!of_data) { - ret = -ENOMEM; - goto err_clocks; - } - - of_data->clks = clocks; - of_data->clk_num = MAX77686_CLKS_NUM; - ret = of_clk_add_provider(iodev->dev->of_node, - of_clk_src_onecell_get, of_data); - if (ret) { - dev_err(&pdev->dev, "failed to register OF clock provider\n"); - goto err_clocks; - } - } - - return 0; - -err_clocks: - for (--i; i >= 0; --i) { - clkdev_drop(max77686_clks[i]->lookup); - clk_unregister(max77686_clks[i]->hw.clk); - } - - return ret; + return max_gen_clk_probe(pdev, iodev->regmap, MAX77686_REG_32KHZ, + max77686_clks_init, MAX77686_CLKS_NUM); } static int max77686_clk_remove(struct platform_device *pdev) { - struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); - struct clk **clocks = platform_get_drvdata(pdev); - int i; - - if (iodev->dev->of_node) - of_clk_del_provider(iodev->dev->of_node); - - for (i = 0; i < MAX77686_CLKS_NUM; i++) { - struct clk_hw *hw = __clk_get_hw(clocks[i]); - struct max77686_clk *max77686 = to_max77686_clk(hw); - - clkdev_drop(max77686->lookup); - clk_unregister(clocks[i]); - } - return 0; + return max_gen_clk_remove(pdev, MAX77686_CLKS_NUM); } static const struct platform_device_id max77686_clk_id[] = { @@ -228,24 +73,13 @@ MODULE_DEVICE_TABLE(platform, max77686_clk_id); static struct platform_driver max77686_clk_driver = { .driver = { .name = "max77686-clk", - .owner = THIS_MODULE, }, .probe = max77686_clk_probe, .remove = max77686_clk_remove, .id_table = max77686_clk_id, }; -static int __init max77686_clk_init(void) -{ - return platform_driver_register(&max77686_clk_driver); -} -subsys_initcall(max77686_clk_init); - -static void __init max77686_clk_cleanup(void) -{ - platform_driver_unregister(&max77686_clk_driver); -} -module_exit(max77686_clk_cleanup); +module_platform_driver(max77686_clk_driver); MODULE_DESCRIPTION("MAXIM 77686 Clock Driver"); MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>"); diff --git a/drivers/clk/clk-max77802.c b/drivers/clk/clk-max77802.c new file mode 100644 index 00000000000..0729dc723a8 --- /dev/null +++ b/drivers/clk/clk-max77802.c @@ -0,0 +1,97 @@ +/* + * clk-max77802.c - Clock driver for Maxim 77802 + * + * Copyright (C) 2014 Google, Inc + * + * Copyright (C) 2012 Samsung Electornics + * Jonghwa Lee <jonghwa3.lee@samsung.com> + * + * 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. + * + * This driver is based on clk-max77686.c + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/mfd/max77686-private.h> +#include <linux/clk-provider.h> +#include <linux/mutex.h> +#include <linux/clkdev.h> + +#include <dt-bindings/clock/maxim,max77802.h> +#include "clk-max-gen.h" + +#define MAX77802_CLOCK_OPMODE_MASK 0x1 +#define MAX77802_CLOCK_LOW_JITTER_SHIFT 0x3 + +static struct clk_init_data max77802_clks_init[MAX77802_CLKS_NUM] = { + [MAX77802_CLK_32K_AP] = { + .name = "32khz_ap", + .ops = &max_gen_clk_ops, + .flags = CLK_IS_ROOT, + }, + [MAX77802_CLK_32K_CP] = { + .name = "32khz_cp", + .ops = &max_gen_clk_ops, + .flags = CLK_IS_ROOT, + }, +}; + +static int max77802_clk_probe(struct platform_device *pdev) +{ + struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent); + int ret; + + ret = max_gen_clk_probe(pdev, iodev->regmap, MAX77802_REG_32KHZ, + max77802_clks_init, MAX77802_CLKS_NUM); + + if (ret) { + dev_err(&pdev->dev, "generic probe failed %d\n", ret); + return ret; + } + + /* Enable low-jitter mode on the 32khz clocks. */ + ret = regmap_update_bits(iodev->regmap, MAX77802_REG_32KHZ, + 1 << MAX77802_CLOCK_LOW_JITTER_SHIFT, + 1 << MAX77802_CLOCK_LOW_JITTER_SHIFT); + if (ret < 0) + dev_err(&pdev->dev, "failed to enable low-jitter mode\n"); + + return ret; +} + +static int max77802_clk_remove(struct platform_device *pdev) +{ + return max_gen_clk_remove(pdev, MAX77802_CLKS_NUM); +} + +static const struct platform_device_id max77802_clk_id[] = { + { "max77802-clk", 0}, + { }, +}; +MODULE_DEVICE_TABLE(platform, max77802_clk_id); + +static struct platform_driver max77802_clk_driver = { + .driver = { + .name = "max77802-clk", + }, + .probe = max77802_clk_probe, + .remove = max77802_clk_remove, + .id_table = max77802_clk_id, +}; + +module_platform_driver(max77802_clk_driver); + +MODULE_DESCRIPTION("MAXIM 77802 Clock Driver"); +MODULE_AUTHOR("Javier Martinez Canillas <javier.martinez@collabora.co.uk>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/clk-palmas.c b/drivers/clk/clk-palmas.c index 781630e1372..8d459923a15 100644 --- a/drivers/clk/clk-palmas.c +++ b/drivers/clk/clk-palmas.c @@ -292,7 +292,6 @@ static int palmas_clks_remove(struct platform_device *pdev) static struct platform_driver palmas_clks_driver = { .driver = { .name = "palmas-clk", - .owner = THIS_MODULE, .of_match_table = palmas_clks_of_match, }, .probe = palmas_clks_probe, diff --git a/drivers/clk/clk-twl6040.c b/drivers/clk/clk-twl6040.c index 1ada79a2805..4a755135bcd 100644 --- a/drivers/clk/clk-twl6040.c +++ b/drivers/clk/clk-twl6040.c @@ -112,7 +112,6 @@ static int twl6040_clk_remove(struct platform_device *pdev) static struct platform_driver twl6040_clk_driver = { .driver = { .name = "twl6040-clk", - .owner = THIS_MODULE, }, .probe = twl6040_clk_probe, .remove = twl6040_clk_remove, diff --git a/drivers/clk/clk-wm831x.c b/drivers/clk/clk-wm831x.c index b131041c8f4..ef67719f4e5 100644 --- a/drivers/clk/clk-wm831x.c +++ b/drivers/clk/clk-wm831x.c @@ -395,7 +395,6 @@ static struct platform_driver wm831x_clk_driver = { .probe = wm831x_clk_probe, .driver = { .name = "wm831x-clk", - .owner = THIS_MODULE, }, }; diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index bacc06ff939..4896ae9e23d 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -100,6 +100,8 @@ static void clk_enable_unlock(unsigned long flags) static struct dentry *rootdir; static int inited = 0; +static DEFINE_MUTEX(clk_debug_lock); +static HLIST_HEAD(clk_debug_list); static struct hlist_head *all_lists[] = { &clk_root_list, @@ -117,11 +119,11 @@ static void clk_summary_show_one(struct seq_file *s, struct clk *c, int level) if (!c) return; - seq_printf(s, "%*s%-*s %11d %12d %11lu %10lu\n", + seq_printf(s, "%*s%-*s %11d %12d %11lu %10lu %-3d\n", level * 3 + 1, "", 30 - level * 3, c->name, c->enable_count, c->prepare_count, clk_get_rate(c), - clk_get_accuracy(c)); + clk_get_accuracy(c), clk_get_phase(c)); } static void clk_summary_show_subtree(struct seq_file *s, struct clk *c, @@ -143,8 +145,8 @@ static int clk_summary_show(struct seq_file *s, void *data) struct clk *c; struct hlist_head **lists = (struct hlist_head **)s->private; - seq_puts(s, " clock enable_cnt prepare_cnt rate accuracy\n"); - seq_puts(s, "--------------------------------------------------------------------------------\n"); + seq_puts(s, " clock enable_cnt prepare_cnt rate accuracy phase\n"); + seq_puts(s, "----------------------------------------------------------------------------------------\n"); clk_prepare_lock(); @@ -180,6 +182,7 @@ static void clk_dump_one(struct seq_file *s, struct clk *c, int level) seq_printf(s, "\"prepare_count\": %d,", c->prepare_count); seq_printf(s, "\"rate\": %lu", clk_get_rate(c)); seq_printf(s, "\"accuracy\": %lu", clk_get_accuracy(c)); + seq_printf(s, "\"phase\": %d", clk_get_phase(c)); } static void clk_dump_subtree(struct seq_file *s, struct clk *c, int level) @@ -264,6 +267,11 @@ static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry) if (!d) goto err_out; + d = debugfs_create_u32("clk_phase", S_IRUGO, clk->dentry, + (u32 *)&clk->phase); + if (!d) + goto err_out; + d = debugfs_create_x32("clk_flags", S_IRUGO, clk->dentry, (u32 *)&clk->flags); if (!d) @@ -300,28 +308,6 @@ out: return ret; } -/* caller must hold prepare_lock */ -static int clk_debug_create_subtree(struct clk *clk, struct dentry *pdentry) -{ - struct clk *child; - int ret = -EINVAL;; - - if (!clk || !pdentry) - goto out; - - ret = clk_debug_create_one(clk, pdentry); - - if (ret) - goto out; - - hlist_for_each_entry(child, &clk->children, child_node) - clk_debug_create_subtree(child, pdentry); - - ret = 0; -out: - return ret; -} - /** * clk_debug_register - add a clk node to the debugfs clk tree * @clk: the clk being added to the debugfs clk tree @@ -329,20 +315,21 @@ out: * Dynamically adds a clk to the debugfs clk tree if debugfs has been * initialized. Otherwise it bails out early since the debugfs clk tree * will be created lazily by clk_debug_init as part of a late_initcall. - * - * Caller must hold prepare_lock. Only clk_init calls this function (so - * far) so this is taken care. */ static int clk_debug_register(struct clk *clk) { int ret = 0; + mutex_lock(&clk_debug_lock); + hlist_add_head(&clk->debug_node, &clk_debug_list); + if (!inited) - goto out; + goto unlock; - ret = clk_debug_create_subtree(clk, rootdir); + ret = clk_debug_create_one(clk, rootdir); +unlock: + mutex_unlock(&clk_debug_lock); -out: return ret; } @@ -353,12 +340,18 @@ out: * Dynamically removes a clk and all it's children clk nodes from the * debugfs clk tree if clk->dentry points to debugfs created by * clk_debug_register in __clk_init. - * - * Caller must hold prepare_lock. */ static void clk_debug_unregister(struct clk *clk) { + mutex_lock(&clk_debug_lock); + if (!clk->dentry) + goto out; + + hlist_del_init(&clk->debug_node); debugfs_remove_recursive(clk->dentry); + clk->dentry = NULL; +out: + mutex_unlock(&clk_debug_lock); } struct dentry *clk_debugfs_add_file(struct clk *clk, char *name, umode_t mode, @@ -415,17 +408,12 @@ static int __init clk_debug_init(void) if (!d) return -ENOMEM; - clk_prepare_lock(); - - hlist_for_each_entry(clk, &clk_root_list, child_node) - clk_debug_create_subtree(clk, rootdir); - - hlist_for_each_entry(clk, &clk_orphan_list, child_node) - clk_debug_create_subtree(clk, rootdir); + mutex_lock(&clk_debug_lock); + hlist_for_each_entry(clk, &clk_debug_list, debug_node) + clk_debug_create_one(clk, rootdir); inited = 1; - - clk_prepare_unlock(); + mutex_unlock(&clk_debug_lock); return 0; } @@ -1744,6 +1732,77 @@ out: EXPORT_SYMBOL_GPL(clk_set_parent); /** + * clk_set_phase - adjust the phase shift of a clock signal + * @clk: clock signal source + * @degrees: number of degrees the signal is shifted + * + * Shifts the phase of a clock signal by the specified + * degrees. Returns 0 on success, -EERROR otherwise. + * + * This function makes no distinction about the input or reference + * signal that we adjust the clock signal phase against. For example + * phase locked-loop clock signal generators we may shift phase with + * respect to feedback clock signal input, but for other cases the + * clock phase may be shifted with respect to some other, unspecified + * signal. + * + * Additionally the concept of phase shift does not propagate through + * the clock tree hierarchy, which sets it apart from clock rates and + * clock accuracy. A parent clock phase attribute does not have an + * impact on the phase attribute of a child clock. + */ +int clk_set_phase(struct clk *clk, int degrees) +{ + int ret = 0; + + if (!clk) + goto out; + + /* sanity check degrees */ + degrees %= 360; + if (degrees < 0) + degrees += 360; + + clk_prepare_lock(); + + if (!clk->ops->set_phase) + goto out_unlock; + + ret = clk->ops->set_phase(clk->hw, degrees); + + if (!ret) + clk->phase = degrees; + +out_unlock: + clk_prepare_unlock(); + +out: + return ret; +} + +/** + * clk_get_phase - return the phase shift of a clock signal + * @clk: clock signal source + * + * Returns the phase shift of a clock node in degrees, otherwise returns + * -EERROR. + */ +int clk_get_phase(struct clk *clk) +{ + int ret = 0; + + if (!clk) + goto out; + + clk_prepare_lock(); + ret = clk->phase; + clk_prepare_unlock(); + +out: + return ret; +} + +/** * __clk_init - initialize the data structures in a struct clk * @dev: device initializing this clk, placeholder for now * @clk: clk being initialized @@ -1862,6 +1921,16 @@ int __clk_init(struct device *dev, struct clk *clk) clk->accuracy = 0; /* + * Set clk's phase. + * Since a phase is by definition relative to its parent, just + * query the current clock phase, or just assume it's in phase. + */ + if (clk->ops->get_phase) + clk->phase = clk->ops->get_phase(clk->hw); + else + clk->phase = 0; + + /* * Set clk's rate. The preferred method is to use .recalc_rate. For * simple clocks and lazy developers the default fallback is to use the * parent's rate. If a clock doesn't have a parent (or is orphaned) @@ -2092,14 +2161,16 @@ void clk_unregister(struct clk *clk) { unsigned long flags; - if (!clk || WARN_ON_ONCE(IS_ERR(clk))) - return; + if (!clk || WARN_ON_ONCE(IS_ERR(clk))) + return; + + clk_debug_unregister(clk); clk_prepare_lock(); if (clk->ops == &clk_nodrv_ops) { pr_err("%s: unregistered clock: %s\n", __func__, clk->name); - goto out; + return; } /* * Assign empty clock ops for consumers that might still hold @@ -2118,16 +2189,13 @@ void clk_unregister(struct clk *clk) clk_set_parent(child, NULL); } - clk_debug_unregister(clk); - hlist_del_init(&clk->child_node); if (clk->prepare_count) pr_warn("%s: unregistering prepared clock: %s\n", __func__, clk->name); - kref_put(&clk->ref, __clk_release); -out: + clk_prepare_unlock(); } EXPORT_SYMBOL_GPL(clk_unregister); diff --git a/drivers/clk/hisilicon/clk-hix5hd2.c b/drivers/clk/hisilicon/clk-hix5hd2.c index e5fcfb4e32e..3f369c60fe5 100644 --- a/drivers/clk/hisilicon/clk-hix5hd2.c +++ b/drivers/clk/hisilicon/clk-hix5hd2.c @@ -9,6 +9,8 @@ #include <linux/of_address.h> #include <dt-bindings/clock/hix5hd2-clock.h> +#include <linux/slab.h> +#include <linux/delay.h> #include "clk.h" static struct hisi_fixed_rate_clock hix5hd2_fixed_rate_clks[] __initdata = { @@ -48,9 +50,9 @@ static const char *sfc_mux_p[] __initconst = { "24m", "150m", "200m", "100m", "75m", }; static u32 sfc_mux_table[] = {0, 4, 5, 6, 7}; -static const char *sdio1_mux_p[] __initconst = { +static const char *sdio_mux_p[] __initconst = { "75m", "100m", "50m", "15m", }; -static u32 sdio1_mux_table[] = {0, 1, 2, 3}; +static u32 sdio_mux_table[] = {0, 1, 2, 3}; static const char *fephy_mux_p[] __initconst = { "25m", "125m"}; static u32 fephy_mux_table[] = {0, 1}; @@ -59,28 +61,243 @@ static u32 fephy_mux_table[] = {0, 1}; static struct hisi_mux_clock hix5hd2_mux_clks[] __initdata = { { HIX5HD2_SFC_MUX, "sfc_mux", sfc_mux_p, ARRAY_SIZE(sfc_mux_p), CLK_SET_RATE_PARENT, 0x5c, 8, 3, 0, sfc_mux_table, }, - { HIX5HD2_MMC_MUX, "mmc_mux", sdio1_mux_p, ARRAY_SIZE(sdio1_mux_p), - CLK_SET_RATE_PARENT, 0xa0, 8, 2, 0, sdio1_mux_table, }, + { HIX5HD2_MMC_MUX, "mmc_mux", sdio_mux_p, ARRAY_SIZE(sdio_mux_p), + CLK_SET_RATE_PARENT, 0xa0, 8, 2, 0, sdio_mux_table, }, + { HIX5HD2_SD_MUX, "sd_mux", sdio_mux_p, ARRAY_SIZE(sdio_mux_p), + CLK_SET_RATE_PARENT, 0x9c, 8, 2, 0, sdio_mux_table, }, { HIX5HD2_FEPHY_MUX, "fephy_mux", fephy_mux_p, ARRAY_SIZE(fephy_mux_p), CLK_SET_RATE_PARENT, 0x120, 8, 2, 0, fephy_mux_table, }, }; static struct hisi_gate_clock hix5hd2_gate_clks[] __initdata = { - /*sfc*/ + /* sfc */ { HIX5HD2_SFC_CLK, "clk_sfc", "sfc_mux", CLK_SET_RATE_PARENT, 0x5c, 0, 0, }, { HIX5HD2_SFC_RST, "rst_sfc", "clk_sfc", CLK_SET_RATE_PARENT, 0x5c, 4, CLK_GATE_SET_TO_DISABLE, }, - /*sdio1*/ + /* sdio0 */ + { HIX5HD2_SD_BIU_CLK, "clk_sd_biu", "200m", + CLK_SET_RATE_PARENT, 0x9c, 0, 0, }, + { HIX5HD2_SD_CIU_CLK, "clk_sd_ciu", "sd_mux", + CLK_SET_RATE_PARENT, 0x9c, 1, 0, }, + { HIX5HD2_SD_CIU_RST, "rst_sd_ciu", "clk_sd_ciu", + CLK_SET_RATE_PARENT, 0x9c, 4, CLK_GATE_SET_TO_DISABLE, }, + /* sdio1 */ { HIX5HD2_MMC_BIU_CLK, "clk_mmc_biu", "200m", CLK_SET_RATE_PARENT, 0xa0, 0, 0, }, { HIX5HD2_MMC_CIU_CLK, "clk_mmc_ciu", "mmc_mux", CLK_SET_RATE_PARENT, 0xa0, 1, 0, }, { HIX5HD2_MMC_CIU_RST, "rst_mmc_ciu", "clk_mmc_ciu", CLK_SET_RATE_PARENT, 0xa0, 4, CLK_GATE_SET_TO_DISABLE, }, + /* gsf */ + { HIX5HD2_FWD_BUS_CLK, "clk_fwd_bus", NULL, 0, 0xcc, 0, 0, }, + { HIX5HD2_FWD_SYS_CLK, "clk_fwd_sys", "clk_fwd_bus", 0, 0xcc, 5, 0, }, + { HIX5HD2_MAC0_PHY_CLK, "clk_fephy", "clk_fwd_sys", + CLK_SET_RATE_PARENT, 0x120, 0, 0, }, + /* wdg0 */ + { HIX5HD2_WDG0_CLK, "clk_wdg0", "24m", + CLK_SET_RATE_PARENT, 0x178, 0, 0, }, + { HIX5HD2_WDG0_RST, "rst_wdg0", "clk_wdg0", + CLK_SET_RATE_PARENT, 0x178, 4, CLK_GATE_SET_TO_DISABLE, }, + /* I2C */ + {HIX5HD2_I2C0_CLK, "clk_i2c0", "100m", + CLK_SET_RATE_PARENT, 0x06c, 4, 0, }, + {HIX5HD2_I2C0_RST, "rst_i2c0", "clk_i2c0", + CLK_SET_RATE_PARENT, 0x06c, 5, CLK_GATE_SET_TO_DISABLE, }, + {HIX5HD2_I2C1_CLK, "clk_i2c1", "100m", + CLK_SET_RATE_PARENT, 0x06c, 8, 0, }, + {HIX5HD2_I2C1_RST, "rst_i2c1", "clk_i2c1", + CLK_SET_RATE_PARENT, 0x06c, 9, CLK_GATE_SET_TO_DISABLE, }, + {HIX5HD2_I2C2_CLK, "clk_i2c2", "100m", + CLK_SET_RATE_PARENT, 0x06c, 12, 0, }, + {HIX5HD2_I2C2_RST, "rst_i2c2", "clk_i2c2", + CLK_SET_RATE_PARENT, 0x06c, 13, CLK_GATE_SET_TO_DISABLE, }, + {HIX5HD2_I2C3_CLK, "clk_i2c3", "100m", + CLK_SET_RATE_PARENT, 0x06c, 16, 0, }, + {HIX5HD2_I2C3_RST, "rst_i2c3", "clk_i2c3", + CLK_SET_RATE_PARENT, 0x06c, 17, CLK_GATE_SET_TO_DISABLE, }, + {HIX5HD2_I2C4_CLK, "clk_i2c4", "100m", + CLK_SET_RATE_PARENT, 0x06c, 20, 0, }, + {HIX5HD2_I2C4_RST, "rst_i2c4", "clk_i2c4", + CLK_SET_RATE_PARENT, 0x06c, 21, CLK_GATE_SET_TO_DISABLE, }, + {HIX5HD2_I2C5_CLK, "clk_i2c5", "100m", + CLK_SET_RATE_PARENT, 0x06c, 0, 0, }, + {HIX5HD2_I2C5_RST, "rst_i2c5", "clk_i2c5", + CLK_SET_RATE_PARENT, 0x06c, 1, CLK_GATE_SET_TO_DISABLE, }, }; +enum hix5hd2_clk_type { + TYPE_COMPLEX, + TYPE_ETHER, +}; + +struct hix5hd2_complex_clock { + const char *name; + const char *parent_name; + u32 id; + u32 ctrl_reg; + u32 ctrl_clk_mask; + u32 ctrl_rst_mask; + u32 phy_reg; + u32 phy_clk_mask; + u32 phy_rst_mask; + enum hix5hd2_clk_type type; +}; + +struct hix5hd2_clk_complex { + struct clk_hw hw; + u32 id; + void __iomem *ctrl_reg; + u32 ctrl_clk_mask; + u32 ctrl_rst_mask; + void __iomem *phy_reg; + u32 phy_clk_mask; + u32 phy_rst_mask; +}; + +static struct hix5hd2_complex_clock hix5hd2_complex_clks[] __initdata = { + {"clk_mac0", "clk_fephy", HIX5HD2_MAC0_CLK, + 0xcc, 0xa, 0x500, 0x120, 0, 0x10, TYPE_ETHER}, + {"clk_mac1", "clk_fwd_sys", HIX5HD2_MAC1_CLK, + 0xcc, 0x14, 0xa00, 0x168, 0x2, 0, TYPE_ETHER}, + {"clk_sata", NULL, HIX5HD2_SATA_CLK, + 0xa8, 0x1f, 0x300, 0xac, 0x1, 0x0, TYPE_COMPLEX}, + {"clk_usb", NULL, HIX5HD2_USB_CLK, + 0xb8, 0xff, 0x3f000, 0xbc, 0x7, 0x3f00, TYPE_COMPLEX}, +}; + +#define to_complex_clk(_hw) container_of(_hw, struct hix5hd2_clk_complex, hw) + +static int clk_ether_prepare(struct clk_hw *hw) +{ + struct hix5hd2_clk_complex *clk = to_complex_clk(hw); + u32 val; + + val = readl_relaxed(clk->ctrl_reg); + val |= clk->ctrl_clk_mask | clk->ctrl_rst_mask; + writel_relaxed(val, clk->ctrl_reg); + val &= ~(clk->ctrl_rst_mask); + writel_relaxed(val, clk->ctrl_reg); + + val = readl_relaxed(clk->phy_reg); + val |= clk->phy_clk_mask; + val &= ~(clk->phy_rst_mask); + writel_relaxed(val, clk->phy_reg); + mdelay(10); + + val &= ~(clk->phy_clk_mask); + val |= clk->phy_rst_mask; + writel_relaxed(val, clk->phy_reg); + mdelay(10); + + val |= clk->phy_clk_mask; + val &= ~(clk->phy_rst_mask); + writel_relaxed(val, clk->phy_reg); + mdelay(30); + return 0; +} + +static void clk_ether_unprepare(struct clk_hw *hw) +{ + struct hix5hd2_clk_complex *clk = to_complex_clk(hw); + u32 val; + + val = readl_relaxed(clk->ctrl_reg); + val &= ~(clk->ctrl_clk_mask); + writel_relaxed(val, clk->ctrl_reg); +} + +static struct clk_ops clk_ether_ops = { + .prepare = clk_ether_prepare, + .unprepare = clk_ether_unprepare, +}; + +static int clk_complex_enable(struct clk_hw *hw) +{ + struct hix5hd2_clk_complex *clk = to_complex_clk(hw); + u32 val; + + val = readl_relaxed(clk->ctrl_reg); + val |= clk->ctrl_clk_mask; + val &= ~(clk->ctrl_rst_mask); + writel_relaxed(val, clk->ctrl_reg); + + val = readl_relaxed(clk->phy_reg); + val |= clk->phy_clk_mask; + val &= ~(clk->phy_rst_mask); + writel_relaxed(val, clk->phy_reg); + + return 0; +} + +static void clk_complex_disable(struct clk_hw *hw) +{ + struct hix5hd2_clk_complex *clk = to_complex_clk(hw); + u32 val; + + val = readl_relaxed(clk->ctrl_reg); + val |= clk->ctrl_rst_mask; + val &= ~(clk->ctrl_clk_mask); + writel_relaxed(val, clk->ctrl_reg); + + val = readl_relaxed(clk->phy_reg); + val |= clk->phy_rst_mask; + val &= ~(clk->phy_clk_mask); + writel_relaxed(val, clk->phy_reg); +} + +static struct clk_ops clk_complex_ops = { + .enable = clk_complex_enable, + .disable = clk_complex_disable, +}; + +void __init hix5hd2_clk_register_complex(struct hix5hd2_complex_clock *clks, + int nums, struct hisi_clock_data *data) +{ + void __iomem *base = data->base; + int i; + + for (i = 0; i < nums; i++) { + struct hix5hd2_clk_complex *p_clk; + struct clk *clk; + struct clk_init_data init; + + p_clk = kzalloc(sizeof(*p_clk), GFP_KERNEL); + if (!p_clk) + return; + + init.name = clks[i].name; + if (clks[i].type == TYPE_ETHER) + init.ops = &clk_ether_ops; + else + init.ops = &clk_complex_ops; + + init.flags = CLK_IS_BASIC; + init.parent_names = + (clks[i].parent_name ? &clks[i].parent_name : NULL); + init.num_parents = (clks[i].parent_name ? 1 : 0); + + p_clk->ctrl_reg = base + clks[i].ctrl_reg; + p_clk->ctrl_clk_mask = clks[i].ctrl_clk_mask; + p_clk->ctrl_rst_mask = clks[i].ctrl_rst_mask; + p_clk->phy_reg = base + clks[i].phy_reg; + p_clk->phy_clk_mask = clks[i].phy_clk_mask; + p_clk->phy_rst_mask = clks[i].phy_rst_mask; + p_clk->hw.init = &init; + + clk = clk_register(NULL, &p_clk->hw); + if (IS_ERR(clk)) { + kfree(p_clk); + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + + data->clk_data.clks[clks[i].id] = clk; + } +} + static void __init hix5hd2_clk_init(struct device_node *np) { struct hisi_clock_data *clk_data; @@ -96,6 +313,9 @@ static void __init hix5hd2_clk_init(struct device_node *np) clk_data); hisi_clk_register_gate(hix5hd2_gate_clks, ARRAY_SIZE(hix5hd2_gate_clks), clk_data); + hix5hd2_clk_register_complex(hix5hd2_complex_clks, + ARRAY_SIZE(hix5hd2_complex_clks), + clk_data); } CLK_OF_DECLARE(hix5hd2_clk, "hisilicon,hix5hd2-clock", hix5hd2_clk_init); diff --git a/drivers/clk/mvebu/armada-370.c b/drivers/clk/mvebu/armada-370.c index bef198a8386..756f0f39d6a 100644 --- a/drivers/clk/mvebu/armada-370.c +++ b/drivers/clk/mvebu/armada-370.c @@ -23,6 +23,7 @@ */ #define SARL 0 /* Low part [0:31] */ +#define SARL_A370_SSCG_ENABLE BIT(10) #define SARL_A370_PCLK_FREQ_OPT 11 #define SARL_A370_PCLK_FREQ_OPT_MASK 0xF #define SARL_A370_FAB_FREQ_OPT 15 @@ -133,10 +134,17 @@ static void __init a370_get_clk_ratio( } } +static bool a370_is_sscg_enabled(void __iomem *sar) +{ + return !(readl(sar) & SARL_A370_SSCG_ENABLE); +} + static const struct coreclk_soc_desc a370_coreclks = { .get_tclk_freq = a370_get_tclk_freq, .get_cpu_freq = a370_get_cpu_freq, .get_clk_ratio = a370_get_clk_ratio, + .is_sscg_enabled = a370_is_sscg_enabled, + .fix_sscg_deviation = kirkwood_fix_sscg_deviation, .ratios = a370_coreclk_ratios, .num_ratios = ARRAY_SIZE(a370_coreclk_ratios), }; diff --git a/drivers/clk/mvebu/armada-375.c b/drivers/clk/mvebu/armada-375.c index c991a4d95e1..c7af2242b79 100644 --- a/drivers/clk/mvebu/armada-375.c +++ b/drivers/clk/mvebu/armada-375.c @@ -27,14 +27,14 @@ * all modified at the same time, and not separately as for the Armada * 370 or the Armada XP SoCs. * - * SAR0[21:17] : CPU frequency DDR frequency L2 frequency + * SAR1[21:17] : CPU frequency DDR frequency L2 frequency * 6 = 400 MHz 400 MHz 200 MHz * 15 = 600 MHz 600 MHz 300 MHz * 21 = 800 MHz 534 MHz 400 MHz * 25 = 1000 MHz 500 MHz 500 MHz * others reserved. * - * SAR0[22] : TCLK frequency + * SAR1[22] : TCLK frequency * 0 = 166 MHz * 1 = 200 MHz */ diff --git a/drivers/clk/mvebu/common.c b/drivers/clk/mvebu/common.c index 25ceccf939a..b7fcb469c87 100644 --- a/drivers/clk/mvebu/common.c +++ b/drivers/clk/mvebu/common.c @@ -26,8 +26,85 @@ * Core Clocks */ +#define SSCG_CONF_MODE(reg) (((reg) >> 16) & 0x3) +#define SSCG_SPREAD_DOWN 0x0 +#define SSCG_SPREAD_UP 0x1 +#define SSCG_SPREAD_CENTRAL 0x2 +#define SSCG_CONF_LOW(reg) (((reg) >> 8) & 0xFF) +#define SSCG_CONF_HIGH(reg) ((reg) & 0xFF) + static struct clk_onecell_data clk_data; +/* + * This function can be used by the Kirkwood, the Armada 370, the + * Armada XP and the Armada 375 SoC. The name of the function was + * chosen following the dt convention: using the first known SoC + * compatible with it. + */ +u32 kirkwood_fix_sscg_deviation(u32 system_clk) +{ + struct device_node *sscg_np = NULL; + void __iomem *sscg_map; + u32 sscg_reg; + s32 low_bound, high_bound; + u64 freq_swing_half; + + sscg_np = of_find_node_by_name(NULL, "sscg"); + if (sscg_np == NULL) { + pr_err("cannot get SSCG register node\n"); + return system_clk; + } + + sscg_map = of_iomap(sscg_np, 0); + if (sscg_map == NULL) { + pr_err("cannot map SSCG register\n"); + goto out; + } + + sscg_reg = readl(sscg_map); + high_bound = SSCG_CONF_HIGH(sscg_reg); + low_bound = SSCG_CONF_LOW(sscg_reg); + + if ((high_bound - low_bound) <= 0) + goto out; + /* + * From Marvell engineer we got the following formula (when + * this code was written, the datasheet was erroneous) + * Spread percentage = 1/96 * (H - L) / H + * H = SSCG_High_Boundary + * L = SSCG_Low_Boundary + * + * As the deviation is half of spread then it lead to the + * following formula in the code. + * + * To avoid an overflow and not lose any significant digit in + * the same time we have to use a 64 bit integer. + */ + + freq_swing_half = (((u64)high_bound - (u64)low_bound) + * (u64)system_clk); + do_div(freq_swing_half, (2 * 96 * high_bound)); + + switch (SSCG_CONF_MODE(sscg_reg)) { + case SSCG_SPREAD_DOWN: + system_clk -= freq_swing_half; + break; + case SSCG_SPREAD_UP: + system_clk += freq_swing_half; + break; + case SSCG_SPREAD_CENTRAL: + default: + break; + } + + iounmap(sscg_map); + +out: + of_node_put(sscg_np); + + return system_clk; +} + void __init mvebu_coreclk_setup(struct device_node *np, const struct coreclk_soc_desc *desc) { @@ -62,6 +139,11 @@ void __init mvebu_coreclk_setup(struct device_node *np, of_property_read_string_index(np, "clock-output-names", 1, &cpuclk_name); rate = desc->get_cpu_freq(base); + + if (desc->is_sscg_enabled && desc->fix_sscg_deviation + && desc->is_sscg_enabled(base)) + rate = desc->fix_sscg_deviation(rate); + clk_data.clks[1] = clk_register_fixed_rate(NULL, cpuclk_name, NULL, CLK_IS_ROOT, rate); WARN_ON(IS_ERR(clk_data.clks[1])); @@ -89,8 +171,10 @@ void __init mvebu_coreclk_setup(struct device_node *np, * Clock Gating Control */ +DEFINE_SPINLOCK(ctrl_gating_lock); + struct clk_gating_ctrl { - spinlock_t lock; + spinlock_t *lock; struct clk **gates; int num_gates; }; @@ -138,7 +222,8 @@ void __init mvebu_clk_gating_setup(struct device_node *np, if (WARN_ON(!ctrl)) goto ctrl_out; - spin_lock_init(&ctrl->lock); + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; /* Count, allocate, and register clock gates */ for (n = 0; desc[n].name;) @@ -155,7 +240,7 @@ void __init mvebu_clk_gating_setup(struct device_node *np, (desc[n].parent) ? desc[n].parent : default_parent; ctrl->gates[n] = clk_register_gate(NULL, desc[n].name, parent, desc[n].flags, base, desc[n].bit_idx, - 0, &ctrl->lock); + 0, ctrl->lock); WARN_ON(IS_ERR(ctrl->gates[n])); } diff --git a/drivers/clk/mvebu/common.h b/drivers/clk/mvebu/common.h index f968b4d9df9..783b5631a45 100644 --- a/drivers/clk/mvebu/common.h +++ b/drivers/clk/mvebu/common.h @@ -17,6 +17,8 @@ #include <linux/kernel.h> +extern spinlock_t ctrl_gating_lock; + struct device_node; struct coreclk_ratio { @@ -28,6 +30,8 @@ struct coreclk_soc_desc { u32 (*get_tclk_freq)(void __iomem *sar); u32 (*get_cpu_freq)(void __iomem *sar); void (*get_clk_ratio)(void __iomem *sar, int id, int *mult, int *div); + bool (*is_sscg_enabled)(void __iomem *sar); + u32 (*fix_sscg_deviation)(u32 system_clk); const struct coreclk_ratio *ratios; int num_ratios; }; @@ -45,4 +49,9 @@ void __init mvebu_coreclk_setup(struct device_node *np, void __init mvebu_clk_gating_setup(struct device_node *np, const struct clk_gating_soc_desc *desc); +/* + * This function is shared among the Kirkwood, Armada 370, Armada XP + * and Armada 375 SoC + */ +u32 kirkwood_fix_sscg_deviation(u32 system_clk); #endif diff --git a/drivers/clk/mvebu/kirkwood.c b/drivers/clk/mvebu/kirkwood.c index ddb666a8650..99550f25975 100644 --- a/drivers/clk/mvebu/kirkwood.c +++ b/drivers/clk/mvebu/kirkwood.c @@ -13,9 +13,11 @@ */ #include <linux/kernel.h> +#include <linux/slab.h> #include <linux/clk-provider.h> #include <linux/io.h> #include <linux/of.h> +#include <linux/of_address.h> #include "common.h" /* @@ -214,7 +216,6 @@ static const struct clk_gating_soc_desc kirkwood_gating_desc[] __initconst = { { "runit", NULL, 7, 0 }, { "xor0", NULL, 8, 0 }, { "audio", NULL, 9, 0 }, - { "powersave", "cpuclk", 11, 0 }, { "sata0", NULL, 14, 0 }, { "sata1", NULL, 15, 0 }, { "xor1", NULL, 16, 0 }, @@ -225,6 +226,101 @@ static const struct clk_gating_soc_desc kirkwood_gating_desc[] __initconst = { { } }; + +/* + * Clock Muxing Control + */ + +struct clk_muxing_soc_desc { + const char *name; + const char **parents; + int num_parents; + int shift; + int width; + unsigned long flags; +}; + +struct clk_muxing_ctrl { + spinlock_t *lock; + struct clk **muxes; + int num_muxes; +}; + +static const char *powersave_parents[] = { + "cpuclk", + "ddrclk", +}; + +static const struct clk_muxing_soc_desc kirkwood_mux_desc[] __initconst = { + { "powersave", powersave_parents, ARRAY_SIZE(powersave_parents), + 11, 1, 0 }, +}; + +#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +static struct clk *clk_muxing_get_src( + struct of_phandle_args *clkspec, void *data) +{ + struct clk_muxing_ctrl *ctrl = (struct clk_muxing_ctrl *)data; + int n; + + if (clkspec->args_count < 1) + return ERR_PTR(-EINVAL); + + for (n = 0; n < ctrl->num_muxes; n++) { + struct clk_mux *mux = + to_clk_mux(__clk_get_hw(ctrl->muxes[n])); + if (clkspec->args[0] == mux->shift) + return ctrl->muxes[n]; + } + return ERR_PTR(-ENODEV); +} + +static void __init kirkwood_clk_muxing_setup(struct device_node *np, + const struct clk_muxing_soc_desc *desc) +{ + struct clk_muxing_ctrl *ctrl; + void __iomem *base; + int n; + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + return; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (WARN_ON(!ctrl)) + goto ctrl_out; + + /* lock must already be initialized */ + ctrl->lock = &ctrl_gating_lock; + + /* Count, allocate, and register clock muxes */ + for (n = 0; desc[n].name;) + n++; + + ctrl->num_muxes = n; + ctrl->muxes = kcalloc(ctrl->num_muxes, sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!ctrl->muxes)) + goto muxes_out; + + for (n = 0; n < ctrl->num_muxes; n++) { + ctrl->muxes[n] = clk_register_mux(NULL, desc[n].name, + desc[n].parents, desc[n].num_parents, + desc[n].flags, base, desc[n].shift, + desc[n].width, desc[n].flags, ctrl->lock); + WARN_ON(IS_ERR(ctrl->muxes[n])); + } + + of_clk_add_provider(np, clk_muxing_get_src, ctrl); + + return; +muxes_out: + kfree(ctrl); +ctrl_out: + iounmap(base); +} + static void __init kirkwood_clk_init(struct device_node *np) { struct device_node *cgnp = @@ -236,8 +332,10 @@ static void __init kirkwood_clk_init(struct device_node *np) else mvebu_coreclk_setup(np, &kirkwood_coreclks); - if (cgnp) + if (cgnp) { mvebu_clk_gating_setup(cgnp, kirkwood_gating_desc); + kirkwood_clk_muxing_setup(cgnp, kirkwood_mux_desc); + } } CLK_OF_DECLARE(kirkwood_clk, "marvell,kirkwood-core-clock", kirkwood_clk_init); diff --git a/drivers/clk/pxa/Makefile b/drivers/clk/pxa/Makefile new file mode 100644 index 00000000000..4ff2abcd500 --- /dev/null +++ b/drivers/clk/pxa/Makefile @@ -0,0 +1,2 @@ +obj-y += clk-pxa.o +obj-$(CONFIG_PXA27x) += clk-pxa27x.o diff --git a/drivers/clk/pxa/clk-pxa.c b/drivers/clk/pxa/clk-pxa.c new file mode 100644 index 00000000000..ef3c05389c0 --- /dev/null +++ b/drivers/clk/pxa/clk-pxa.c @@ -0,0 +1,97 @@ +/* + * Marvell PXA family clocks + * + * Copyright (C) 2014 Robert Jarzmik + * + * Common clock code for PXA clocks ("CKEN" type clocks + DT) + * + * 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; version 2 of the License. + * + */ +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/of.h> + +#include <dt-bindings/clock/pxa-clock.h> +#include "clk-pxa.h" + +DEFINE_SPINLOCK(lock); + +static struct clk *pxa_clocks[CLK_MAX]; +static struct clk_onecell_data onecell_data = { + .clks = pxa_clocks, + .clk_num = CLK_MAX, +}; + +#define to_pxa_clk(_hw) container_of(_hw, struct pxa_clk_cken, hw) + +static unsigned long cken_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct pxa_clk_cken *pclk = to_pxa_clk(hw); + struct clk_fixed_factor *fix; + + if (!pclk->is_in_low_power || pclk->is_in_low_power()) + fix = &pclk->lp; + else + fix = &pclk->hp; + fix->hw.clk = hw->clk; + return clk_fixed_factor_ops.recalc_rate(&fix->hw, parent_rate); +} + +static struct clk_ops cken_rate_ops = { + .recalc_rate = cken_recalc_rate, +}; + +static u8 cken_get_parent(struct clk_hw *hw) +{ + struct pxa_clk_cken *pclk = to_pxa_clk(hw); + + if (!pclk->is_in_low_power) + return 0; + return pclk->is_in_low_power() ? 0 : 1; +} + +static struct clk_ops cken_mux_ops = { + .get_parent = cken_get_parent, + .set_parent = dummy_clk_set_parent, +}; + +void __init clkdev_pxa_register(int ckid, const char *con_id, + const char *dev_id, struct clk *clk) +{ + if (!IS_ERR(clk) && (ckid != CLK_NONE)) + pxa_clocks[ckid] = clk; + if (!IS_ERR(clk)) + clk_register_clkdev(clk, con_id, dev_id); +} + +int __init clk_pxa_cken_init(struct pxa_clk_cken *clks, int nb_clks) +{ + int i; + struct pxa_clk_cken *pclk; + struct clk *clk; + + for (i = 0; i < nb_clks; i++) { + pclk = clks + i; + pclk->gate.lock = &lock; + clk = clk_register_composite(NULL, pclk->name, + pclk->parent_names, 2, + &pclk->hw, &cken_mux_ops, + &pclk->hw, &cken_rate_ops, + &pclk->gate.hw, &clk_gate_ops, + pclk->flags); + clkdev_pxa_register(pclk->ckid, pclk->con_id, pclk->dev_id, + clk); + } + return 0; +} + +static void __init pxa_dt_clocks_init(struct device_node *np) +{ + of_clk_add_provider(np, of_clk_src_onecell_get, &onecell_data); +} +CLK_OF_DECLARE(pxa_clks, "marvell,pxa-clocks", pxa_dt_clocks_init); diff --git a/drivers/clk/pxa/clk-pxa.h b/drivers/clk/pxa/clk-pxa.h new file mode 100644 index 00000000000..5fe219d06b4 --- /dev/null +++ b/drivers/clk/pxa/clk-pxa.h @@ -0,0 +1,107 @@ +/* + * Marvell PXA family clocks + * + * Copyright (C) 2014 Robert Jarzmik + * + * Common clock code for PXA clocks ("CKEN" type clocks + DT) + * + * 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; version 2 of the License. + * + */ +#ifndef _CLK_PXA_ +#define _CLK_PXA_ + +#define PARENTS(name) \ + static const char *name ## _parents[] __initconst +#define MUX_RO_RATE_RO_OPS(name, clk_name) \ + static struct clk_hw name ## _mux_hw; \ + static struct clk_hw name ## _rate_hw; \ + static struct clk_ops name ## _mux_ops = { \ + .get_parent = name ## _get_parent, \ + .set_parent = dummy_clk_set_parent, \ + }; \ + static struct clk_ops name ## _rate_ops = { \ + .recalc_rate = name ## _get_rate, \ + }; \ + static struct clk *clk_register_ ## name(void) \ + { \ + return clk_register_composite(NULL, clk_name, \ + name ## _parents, \ + ARRAY_SIZE(name ## _parents), \ + &name ## _mux_hw, &name ## _mux_ops, \ + &name ## _rate_hw, &name ## _rate_ops, \ + NULL, NULL, CLK_GET_RATE_NOCACHE); \ + } + +#define RATE_RO_OPS(name, clk_name) \ + static struct clk_hw name ## _rate_hw; \ + static struct clk_ops name ## _rate_ops = { \ + .recalc_rate = name ## _get_rate, \ + }; \ + static struct clk *clk_register_ ## name(void) \ + { \ + return clk_register_composite(NULL, clk_name, \ + name ## _parents, \ + ARRAY_SIZE(name ## _parents), \ + NULL, NULL, \ + &name ## _rate_hw, &name ## _rate_ops, \ + NULL, NULL, CLK_GET_RATE_NOCACHE); \ + } + +/* + * CKEN clock type + * This clock takes it source from 2 possible parents : + * - a low power parent + * - a normal parent + * + * +------------+ +-----------+ + * | Low Power | --- | x mult_lp | + * | Clock | | / div_lp |\ + * +------------+ +-----------+ \+-----+ +-----------+ + * | Mux |---| CKEN gate | + * +------------+ +-----------+ /+-----+ +-----------+ + * | High Power | | x mult_hp |/ + * | Clock | --- | / div_hp | + * +------------+ +-----------+ + */ +struct pxa_clk_cken { + struct clk_hw hw; + int ckid; + const char *name; + const char *dev_id; + const char *con_id; + const char **parent_names; + struct clk_fixed_factor lp; + struct clk_fixed_factor hp; + struct clk_gate gate; + bool (*is_in_low_power)(void); + const unsigned long flags; +}; + +#define PXA_CKEN(_dev_id, _con_id, _name, parents, _mult_lp, _div_lp, \ + _mult_hp, _div_hp, is_lp, _cken_reg, _cken_bit, flag) \ + { .ckid = CLK_ ## _name, .name = #_name, \ + .dev_id = _dev_id, .con_id = _con_id, .parent_names = parents,\ + .lp = { .mult = _mult_lp, .div = _div_lp }, \ + .hp = { .mult = _mult_hp, .div = _div_hp }, \ + .is_in_low_power = is_lp, \ + .gate = { .reg = (void __iomem *)_cken_reg, .bit_idx = _cken_bit }, \ + .flags = flag, \ + } +#define PXA_CKEN_1RATE(dev_id, con_id, name, parents, cken_reg, \ + cken_bit, flag) \ + PXA_CKEN(dev_id, con_id, name, parents, 1, 1, 1, 1, \ + NULL, cken_reg, cken_bit, flag) + +static int dummy_clk_set_parent(struct clk_hw *hw, u8 index) +{ + return 0; +} + +extern void clkdev_pxa_register(int ckid, const char *con_id, + const char *dev_id, struct clk *clk); +extern int clk_pxa_cken_init(struct pxa_clk_cken *clks, int nb_clks); + +#endif diff --git a/drivers/clk/pxa/clk-pxa27x.c b/drivers/clk/pxa/clk-pxa27x.c new file mode 100644 index 00000000000..b345cc791e5 --- /dev/null +++ b/drivers/clk/pxa/clk-pxa27x.c @@ -0,0 +1,370 @@ +/* + * Marvell PXA27x family clocks + * + * Copyright (C) 2014 Robert Jarzmik + * + * Heavily inspired from former arch/arm/mach-pxa/clock.c. + * + * 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; version 2 of the License. + * + */ +#include <linux/clk-provider.h> +#include <mach/pxa2xx-regs.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/of.h> + +#include <dt-bindings/clock/pxa-clock.h> +#include "clk-pxa.h" + +#define KHz 1000 +#define MHz (1000 * 1000) + +enum { + PXA_CORE_13Mhz = 0, + PXA_CORE_RUN, + PXA_CORE_TURBO, +}; + +enum { + PXA_BUS_13Mhz = 0, + PXA_BUS_RUN, +}; + +enum { + PXA_LCD_13Mhz = 0, + PXA_LCD_RUN, +}; + +enum { + PXA_MEM_13Mhz = 0, + PXA_MEM_SYSTEM_BUS, + PXA_MEM_RUN, +}; + +static const char * const get_freq_khz[] = { + "core", "run", "cpll", "memory", + "system_bus" +}; + +/* + * Get the clock frequency as reflected by CCSR and the turbo flag. + * We assume these values have been applied via a fcs. + * If info is not 0 we also display the current settings. + */ +unsigned int pxa27x_get_clk_frequency_khz(int info) +{ + struct clk *clk; + unsigned long clks[5]; + int i; + + for (i = 0; i < 5; i++) { + clk = clk_get(NULL, get_freq_khz[i]); + if (IS_ERR(clk)) { + clks[i] = 0; + } else { + clks[i] = clk_get_rate(clk); + clk_put(clk); + } + } + if (info) { + pr_info("Run Mode clock: %ld.%02ldMHz\n", + clks[1] / 1000000, (clks[1] % 1000000) / 10000); + pr_info("Turbo Mode clock: %ld.%02ldMHz\n", + clks[2] / 1000000, (clks[2] % 1000000) / 10000); + pr_info("Memory clock: %ld.%02ldMHz\n", + clks[3] / 1000000, (clks[3] % 1000000) / 10000); + pr_info("System bus clock: %ld.%02ldMHz\n", + clks[4] / 1000000, (clks[4] % 1000000) / 10000); + } + return (unsigned int)clks[0]; +} + +bool pxa27x_is_ppll_disabled(void) +{ + unsigned long ccsr = CCSR; + + return ccsr & (1 << CCCR_PPDIS_BIT); +} + +#define PXA27X_CKEN(dev_id, con_id, parents, mult_hp, div_hp, \ + bit, is_lp, flags) \ + PXA_CKEN(dev_id, con_id, bit, parents, 1, 1, mult_hp, div_hp, \ + is_lp, &CKEN, CKEN_ ## bit, flags) +#define PXA27X_PBUS_CKEN(dev_id, con_id, bit, mult_hp, div_hp, delay) \ + PXA27X_CKEN(dev_id, con_id, pxa27x_pbus_parents, mult_hp, \ + div_hp, bit, pxa27x_is_ppll_disabled, 0) + +PARENTS(pxa27x_pbus) = { "osc_13mhz", "ppll_312mhz" }; +PARENTS(pxa27x_sbus) = { "system_bus", "system_bus" }; +PARENTS(pxa27x_32Mhz_bus) = { "osc_32_768khz", "osc_32_768khz" }; +PARENTS(pxa27x_lcd_bus) = { "lcd_base", "lcd_base" }; +PARENTS(pxa27x_membus) = { "lcd_base", "lcd_base" }; + +#define PXA27X_CKEN_1RATE(dev_id, con_id, bit, parents, delay) \ + PXA_CKEN_1RATE(dev_id, con_id, bit, parents, \ + &CKEN, CKEN_ ## bit, 0) +#define PXA27X_CKEN_1RATE_AO(dev_id, con_id, bit, parents, delay) \ + PXA_CKEN_1RATE(dev_id, con_id, bit, parents, \ + &CKEN, CKEN_ ## bit, CLK_IGNORE_UNUSED) + +static struct pxa_clk_cken pxa27x_clocks[] = { + PXA27X_PBUS_CKEN("pxa2xx-uart.0", NULL, FFUART, 2, 42, 1), + PXA27X_PBUS_CKEN("pxa2xx-uart.1", NULL, BTUART, 2, 42, 1), + PXA27X_PBUS_CKEN("pxa2xx-uart.2", NULL, STUART, 2, 42, 1), + PXA27X_PBUS_CKEN("pxa2xx-i2s", NULL, I2S, 2, 51, 0), + PXA27X_PBUS_CKEN("pxa2xx-i2c.0", NULL, I2C, 2, 19, 0), + PXA27X_PBUS_CKEN("pxa27x-udc", NULL, USB, 2, 13, 5), + PXA27X_PBUS_CKEN("pxa2xx-mci.0", NULL, MMC, 2, 32, 0), + PXA27X_PBUS_CKEN("pxa2xx-ir", "FICPCLK", FICP, 2, 13, 0), + PXA27X_PBUS_CKEN("pxa27x-ohci", NULL, USBHOST, 2, 13, 0), + PXA27X_PBUS_CKEN("pxa2xx-i2c.1", NULL, PWRI2C, 1, 24, 0), + PXA27X_PBUS_CKEN("pxa27x-ssp.0", NULL, SSP1, 1, 24, 0), + PXA27X_PBUS_CKEN("pxa27x-ssp.1", NULL, SSP2, 1, 24, 0), + PXA27X_PBUS_CKEN("pxa27x-ssp.2", NULL, SSP3, 1, 24, 0), + PXA27X_PBUS_CKEN("pxa27x-pwm.0", NULL, PWM0, 1, 24, 0), + PXA27X_PBUS_CKEN("pxa27x-pwm.1", NULL, PWM1, 1, 24, 0), + PXA27X_PBUS_CKEN(NULL, "MSLCLK", MSL, 2, 13, 0), + PXA27X_PBUS_CKEN(NULL, "USIMCLK", USIM, 2, 13, 0), + PXA27X_PBUS_CKEN(NULL, "MSTKCLK", MEMSTK, 2, 32, 0), + PXA27X_PBUS_CKEN(NULL, "AC97CLK", AC97, 1, 1, 0), + PXA27X_PBUS_CKEN(NULL, "AC97CONFCLK", AC97CONF, 1, 1, 0), + PXA27X_PBUS_CKEN(NULL, "OSTIMER0", OSTIMER, 1, 96, 0), + + PXA27X_CKEN_1RATE("pxa27x-keypad", NULL, KEYPAD, + pxa27x_32Mhz_bus_parents, 0), + PXA27X_CKEN_1RATE(NULL, "IMCLK", IM, pxa27x_sbus_parents, 0), + PXA27X_CKEN_1RATE("pxa2xx-fb", NULL, LCD, pxa27x_lcd_bus_parents, 0), + PXA27X_CKEN_1RATE("pxa27x-camera.0", NULL, CAMERA, + pxa27x_lcd_bus_parents, 0), + PXA27X_CKEN_1RATE_AO("pxa2xx-pcmcia", NULL, MEMC, + pxa27x_membus_parents, 0), + +}; + +static unsigned long clk_pxa27x_cpll_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned long clkcfg; + unsigned int t, ht; + unsigned int l, L, n2, N; + unsigned long ccsr = CCSR; + + asm("mrc\tp14, 0, %0, c6, c0, 0" : "=r" (clkcfg)); + t = clkcfg & (1 << 0); + ht = clkcfg & (1 << 2); + + l = ccsr & CCSR_L_MASK; + n2 = (ccsr & CCSR_N2_MASK) >> CCSR_N2_SHIFT; + L = l * parent_rate; + N = (L * n2) / 2; + + return t ? N : L; +} +PARENTS(clk_pxa27x_cpll) = { "osc_13mhz" }; +RATE_RO_OPS(clk_pxa27x_cpll, "cpll"); + +static unsigned long clk_pxa27x_lcd_base_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned int l, osc_forced; + unsigned long ccsr = CCSR; + unsigned long cccr = CCCR; + + l = ccsr & CCSR_L_MASK; + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + if (osc_forced) { + if (cccr & (1 << CCCR_LCD_26_BIT)) + return parent_rate * 2; + else + return parent_rate; + } + + if (l <= 7) + return parent_rate; + if (l <= 16) + return parent_rate / 2; + return parent_rate / 4; +} + +static u8 clk_pxa27x_lcd_base_get_parent(struct clk_hw *hw) +{ + unsigned int osc_forced; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + if (osc_forced) + return PXA_LCD_13Mhz; + else + return PXA_LCD_RUN; +} + +PARENTS(clk_pxa27x_lcd_base) = { "osc_13mhz", "run" }; +MUX_RO_RATE_RO_OPS(clk_pxa27x_lcd_base, "lcd_base"); + +static void __init pxa27x_register_plls(void) +{ + clk_register_fixed_rate(NULL, "osc_13mhz", NULL, + CLK_GET_RATE_NOCACHE | CLK_IS_ROOT, + 13 * MHz); + clk_register_fixed_rate(NULL, "osc_32_768khz", NULL, + CLK_GET_RATE_NOCACHE | CLK_IS_ROOT, + 32768 * KHz); + clk_register_fixed_rate(NULL, "clk_dummy", NULL, CLK_IS_ROOT, 0); + clk_register_fixed_factor(NULL, "ppll_312mhz", "osc_13mhz", 0, 24, 1); +} + +static unsigned long clk_pxa27x_core_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned long clkcfg; + unsigned int t, ht, b, osc_forced; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + asm("mrc\tp14, 0, %0, c6, c0, 0" : "=r" (clkcfg)); + t = clkcfg & (1 << 0); + ht = clkcfg & (1 << 2); + b = clkcfg & (1 << 3); + + if (osc_forced) + return parent_rate; + if (ht) + return parent_rate / 2; + else + return parent_rate; +} + +static u8 clk_pxa27x_core_get_parent(struct clk_hw *hw) +{ + unsigned long clkcfg; + unsigned int t, ht, b, osc_forced; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + if (osc_forced) + return PXA_CORE_13Mhz; + + asm("mrc\tp14, 0, %0, c6, c0, 0" : "=r" (clkcfg)); + t = clkcfg & (1 << 0); + ht = clkcfg & (1 << 2); + b = clkcfg & (1 << 3); + + if (ht || t) + return PXA_CORE_TURBO; + return PXA_CORE_RUN; +} +PARENTS(clk_pxa27x_core) = { "osc_13mhz", "run", "cpll" }; +MUX_RO_RATE_RO_OPS(clk_pxa27x_core, "core"); + +static unsigned long clk_pxa27x_run_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned long ccsr = CCSR; + unsigned int n2 = (ccsr & CCSR_N2_MASK) >> CCSR_N2_SHIFT; + + return (parent_rate / n2) * 2; +} +PARENTS(clk_pxa27x_run) = { "cpll" }; +RATE_RO_OPS(clk_pxa27x_run, "run"); + +static void __init pxa27x_register_core(void) +{ + clk_register_clk_pxa27x_cpll(); + clk_register_clk_pxa27x_run(); + + clkdev_pxa_register(CLK_CORE, "core", NULL, + clk_register_clk_pxa27x_core()); +} + +static unsigned long clk_pxa27x_system_bus_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned long clkcfg; + unsigned int b, osc_forced; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + asm("mrc\tp14, 0, %0, c6, c0, 0" : "=r" (clkcfg)); + b = clkcfg & (1 << 3); + + if (osc_forced) + return parent_rate; + if (b) + return parent_rate / 2; + else + return parent_rate; +} + +static u8 clk_pxa27x_system_bus_get_parent(struct clk_hw *hw) +{ + unsigned int osc_forced; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + if (osc_forced) + return PXA_BUS_13Mhz; + else + return PXA_BUS_RUN; +} + +PARENTS(clk_pxa27x_system_bus) = { "osc_13mhz", "run" }; +MUX_RO_RATE_RO_OPS(clk_pxa27x_system_bus, "system_bus"); + +static unsigned long clk_pxa27x_memory_get_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + unsigned int a, l, osc_forced; + unsigned long cccr = CCCR; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + a = cccr & CCCR_A_BIT; + l = ccsr & CCSR_L_MASK; + + if (osc_forced || a) + return parent_rate; + if (l <= 10) + return parent_rate; + if (l <= 20) + return parent_rate / 2; + return parent_rate / 4; +} + +static u8 clk_pxa27x_memory_get_parent(struct clk_hw *hw) +{ + unsigned int osc_forced, a; + unsigned long cccr = CCCR; + unsigned long ccsr = CCSR; + + osc_forced = ccsr & (1 << CCCR_CPDIS_BIT); + a = cccr & CCCR_A_BIT; + if (osc_forced) + return PXA_MEM_13Mhz; + if (a) + return PXA_MEM_SYSTEM_BUS; + else + return PXA_MEM_RUN; +} + +PARENTS(clk_pxa27x_memory) = { "osc_13mhz", "system_bus", "run" }; +MUX_RO_RATE_RO_OPS(clk_pxa27x_memory, "memory"); + +static void __init pxa27x_base_clocks_init(void) +{ + pxa27x_register_plls(); + pxa27x_register_core(); + clk_register_clk_pxa27x_system_bus(); + clk_register_clk_pxa27x_memory(); + clk_register_clk_pxa27x_lcd_base(); +} + +static int __init pxa27x_clocks_init(void) +{ + pxa27x_base_clocks_init(); + return clk_pxa_cken_init(pxa27x_clocks, ARRAY_SIZE(pxa27x_clocks)); +} +postcore_initcall(pxa27x_clocks_init); diff --git a/drivers/clk/qcom/clk-pll.c b/drivers/clk/qcom/clk-pll.c index 9db03d3b165..b823bc3b625 100644 --- a/drivers/clk/qcom/clk-pll.c +++ b/drivers/clk/qcom/clk-pll.c @@ -97,7 +97,7 @@ static unsigned long clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct clk_pll *pll = to_clk_pll(hw); - u32 l, m, n; + u32 l, m, n, config; unsigned long rate; u64 tmp; @@ -116,13 +116,79 @@ clk_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) do_div(tmp, n); rate += tmp; } + if (pll->post_div_width) { + regmap_read(pll->clkr.regmap, pll->config_reg, &config); + config >>= pll->post_div_shift; + config &= BIT(pll->post_div_width) - 1; + rate /= config + 1; + } + return rate; } +static const +struct pll_freq_tbl *find_freq(const struct pll_freq_tbl *f, unsigned long rate) +{ + if (!f) + return NULL; + + for (; f->freq; f++) + if (rate <= f->freq) + return f; + + return NULL; +} + +static long +clk_pll_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *p_rate, struct clk **p) +{ + struct clk_pll *pll = to_clk_pll(hw); + const struct pll_freq_tbl *f; + + f = find_freq(pll->freq_tbl, rate); + if (!f) + return clk_pll_recalc_rate(hw, *p_rate); + + return f->freq; +} + +static int +clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long p_rate) +{ + struct clk_pll *pll = to_clk_pll(hw); + const struct pll_freq_tbl *f; + bool enabled; + u32 mode; + u32 enable_mask = PLL_OUTCTRL | PLL_BYPASSNL | PLL_RESET_N; + + f = find_freq(pll->freq_tbl, rate); + if (!f) + return -EINVAL; + + regmap_read(pll->clkr.regmap, pll->mode_reg, &mode); + enabled = (mode & enable_mask) == enable_mask; + + if (enabled) + clk_pll_disable(hw); + + regmap_update_bits(pll->clkr.regmap, pll->l_reg, 0x3ff, f->l); + regmap_update_bits(pll->clkr.regmap, pll->m_reg, 0x7ffff, f->m); + regmap_update_bits(pll->clkr.regmap, pll->n_reg, 0x7ffff, f->n); + regmap_write(pll->clkr.regmap, pll->config_reg, f->ibits); + + if (enabled) + clk_pll_enable(hw); + + return 0; +} + const struct clk_ops clk_pll_ops = { .enable = clk_pll_enable, .disable = clk_pll_disable, .recalc_rate = clk_pll_recalc_rate, + .determine_rate = clk_pll_determine_rate, + .set_rate = clk_pll_set_rate, }; EXPORT_SYMBOL_GPL(clk_pll_ops); diff --git a/drivers/clk/qcom/clk-pll.h b/drivers/clk/qcom/clk-pll.h index 3003e996247..c9c0cda306d 100644 --- a/drivers/clk/qcom/clk-pll.h +++ b/drivers/clk/qcom/clk-pll.h @@ -18,6 +18,21 @@ #include "clk-regmap.h" /** + * struct pll_freq_tbl - PLL frequency table + * @l: L value + * @m: M value + * @n: N value + * @ibits: internal values + */ +struct pll_freq_tbl { + unsigned long freq; + u16 l; + u16 m; + u16 n; + u32 ibits; +}; + +/** * struct clk_pll - phase locked loop (PLL) * @l_reg: L register * @m_reg: M register @@ -26,6 +41,7 @@ * @mode_reg: mode register * @status_reg: status register * @status_bit: ANDed with @status_reg to determine if PLL is enabled + * @freq_tbl: PLL frequency table * @hw: handle between common and hardware-specific interfaces */ struct clk_pll { @@ -36,6 +52,10 @@ struct clk_pll { u32 mode_reg; u32 status_reg; u8 status_bit; + u8 post_div_width; + u8 post_div_shift; + + const struct pll_freq_tbl *freq_tbl; struct clk_regmap clkr; }; diff --git a/drivers/clk/qcom/clk-rcg.c b/drivers/clk/qcom/clk-rcg.c index b638c5846db..b6e6959e89a 100644 --- a/drivers/clk/qcom/clk-rcg.c +++ b/drivers/clk/qcom/clk-rcg.c @@ -21,6 +21,7 @@ #include <asm/div64.h> #include "clk-rcg.h" +#include "common.h" static u32 ns_to_src(struct src_sel *s, u32 ns) { @@ -67,16 +68,16 @@ static u8 clk_dyn_rcg_get_parent(struct clk_hw *hw) { struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); int num_parents = __clk_get_num_parents(hw->clk); - u32 ns, ctl; + u32 ns, reg; int bank; int i; struct src_sel *s; - regmap_read(rcg->clkr.regmap, rcg->clkr.enable_reg, &ctl); - bank = reg_to_bank(rcg, ctl); + regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); + bank = reg_to_bank(rcg, reg); s = &rcg->s[bank]; - regmap_read(rcg->clkr.regmap, rcg->ns_reg, &ns); + regmap_read(rcg->clkr.regmap, rcg->ns_reg[bank], &ns); ns = ns_to_src(s, ns); for (i = 0; i < num_parents; i++) @@ -192,90 +193,93 @@ static u32 mn_to_reg(struct mn *mn, u32 m, u32 n, u32 val) static void configure_bank(struct clk_dyn_rcg *rcg, const struct freq_tbl *f) { - u32 ns, md, ctl, *regp; + u32 ns, md, reg; int bank, new_bank; struct mn *mn; struct pre_div *p; struct src_sel *s; bool enabled; - u32 md_reg; - u32 bank_reg; + u32 md_reg, ns_reg; bool banked_mn = !!rcg->mn[1].width; + bool banked_p = !!rcg->p[1].pre_div_width; struct clk_hw *hw = &rcg->clkr.hw; enabled = __clk_is_enabled(hw->clk); - regmap_read(rcg->clkr.regmap, rcg->ns_reg, &ns); - regmap_read(rcg->clkr.regmap, rcg->clkr.enable_reg, &ctl); - - if (banked_mn) { - regp = &ctl; - bank_reg = rcg->clkr.enable_reg; - } else { - regp = &ns; - bank_reg = rcg->ns_reg; - } - - bank = reg_to_bank(rcg, *regp); + regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); + bank = reg_to_bank(rcg, reg); new_bank = enabled ? !bank : bank; + ns_reg = rcg->ns_reg[new_bank]; + regmap_read(rcg->clkr.regmap, ns_reg, &ns); + if (banked_mn) { mn = &rcg->mn[new_bank]; md_reg = rcg->md_reg[new_bank]; ns |= BIT(mn->mnctr_reset_bit); - regmap_write(rcg->clkr.regmap, rcg->ns_reg, ns); + regmap_write(rcg->clkr.regmap, ns_reg, ns); regmap_read(rcg->clkr.regmap, md_reg, &md); md = mn_to_md(mn, f->m, f->n, md); regmap_write(rcg->clkr.regmap, md_reg, md); ns = mn_to_ns(mn, f->m, f->n, ns); - regmap_write(rcg->clkr.regmap, rcg->ns_reg, ns); + regmap_write(rcg->clkr.regmap, ns_reg, ns); - ctl = mn_to_reg(mn, f->m, f->n, ctl); - regmap_write(rcg->clkr.regmap, rcg->clkr.enable_reg, ctl); + /* Two NS registers means mode control is in NS register */ + if (rcg->ns_reg[0] != rcg->ns_reg[1]) { + ns = mn_to_reg(mn, f->m, f->n, ns); + regmap_write(rcg->clkr.regmap, ns_reg, ns); + } else { + reg = mn_to_reg(mn, f->m, f->n, reg); + regmap_write(rcg->clkr.regmap, rcg->bank_reg, reg); + } ns &= ~BIT(mn->mnctr_reset_bit); - regmap_write(rcg->clkr.regmap, rcg->ns_reg, ns); - } else { + regmap_write(rcg->clkr.regmap, ns_reg, ns); + } + + if (banked_p) { p = &rcg->p[new_bank]; ns = pre_div_to_ns(p, f->pre_div - 1, ns); } s = &rcg->s[new_bank]; ns = src_to_ns(s, s->parent_map[f->src], ns); - regmap_write(rcg->clkr.regmap, rcg->ns_reg, ns); + regmap_write(rcg->clkr.regmap, ns_reg, ns); if (enabled) { - *regp ^= BIT(rcg->mux_sel_bit); - regmap_write(rcg->clkr.regmap, bank_reg, *regp); + regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); + reg ^= BIT(rcg->mux_sel_bit); + regmap_write(rcg->clkr.regmap, rcg->bank_reg, reg); } } static int clk_dyn_rcg_set_parent(struct clk_hw *hw, u8 index) { struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); - u32 ns, ctl, md, reg; + u32 ns, md, reg; int bank; struct freq_tbl f = { 0 }; bool banked_mn = !!rcg->mn[1].width; + bool banked_p = !!rcg->p[1].pre_div_width; - regmap_read(rcg->clkr.regmap, rcg->ns_reg, &ns); - regmap_read(rcg->clkr.regmap, rcg->clkr.enable_reg, &ctl); - reg = banked_mn ? ctl : ns; - + regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); bank = reg_to_bank(rcg, reg); + regmap_read(rcg->clkr.regmap, rcg->ns_reg[bank], &ns); + if (banked_mn) { regmap_read(rcg->clkr.regmap, rcg->md_reg[bank], &md); f.m = md_to_m(&rcg->mn[bank], md); f.n = ns_m_to_n(&rcg->mn[bank], ns, f.m); - } else { - f.pre_div = ns_to_pre_div(&rcg->p[bank], ns) + 1; } - f.src = index; + if (banked_p) + f.pre_div = ns_to_pre_div(&rcg->p[bank], ns) + 1; + + f.src = index; configure_bank(rcg, &f); return 0; @@ -336,41 +340,30 @@ clk_dyn_rcg_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) u32 m, n, pre_div, ns, md, mode, reg; int bank; struct mn *mn; + bool banked_p = !!rcg->p[1].pre_div_width; bool banked_mn = !!rcg->mn[1].width; - regmap_read(rcg->clkr.regmap, rcg->ns_reg, &ns); - - if (banked_mn) - regmap_read(rcg->clkr.regmap, rcg->clkr.enable_reg, ®); - else - reg = ns; - + regmap_read(rcg->clkr.regmap, rcg->bank_reg, ®); bank = reg_to_bank(rcg, reg); + regmap_read(rcg->clkr.regmap, rcg->ns_reg[bank], &ns); + m = n = pre_div = mode = 0; + if (banked_mn) { mn = &rcg->mn[bank]; regmap_read(rcg->clkr.regmap, rcg->md_reg[bank], &md); m = md_to_m(mn, md); n = ns_m_to_n(mn, ns, m); + /* Two NS registers means mode control is in NS register */ + if (rcg->ns_reg[0] != rcg->ns_reg[1]) + reg = ns; mode = reg_to_mnctr_mode(mn, reg); - return calc_rate(parent_rate, m, n, mode, 0); - } else { - pre_div = ns_to_pre_div(&rcg->p[bank], ns); - return calc_rate(parent_rate, 0, 0, 0, pre_div); } -} -static const -struct freq_tbl *find_freq(const struct freq_tbl *f, unsigned long rate) -{ - if (!f) - return NULL; - - for (; f->freq; f++) - if (rate <= f->freq) - return f; + if (banked_p) + pre_div = ns_to_pre_div(&rcg->p[bank], ns); - return NULL; + return calc_rate(parent_rate, m, n, mode, pre_div); } static long _freq_tbl_determine_rate(struct clk_hw *hw, @@ -379,7 +372,7 @@ static long _freq_tbl_determine_rate(struct clk_hw *hw, { unsigned long clk_flags; - f = find_freq(f, rate); + f = qcom_find_freq(f, rate); if (!f) return -EINVAL; @@ -477,7 +470,7 @@ static int clk_rcg_set_rate(struct clk_hw *hw, unsigned long rate, struct clk_rcg *rcg = to_clk_rcg(hw); const struct freq_tbl *f; - f = find_freq(rcg->freq_tbl, rate); + f = qcom_find_freq(rcg->freq_tbl, rate); if (!f) return -EINVAL; @@ -497,7 +490,7 @@ static int __clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate) struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw); const struct freq_tbl *f; - f = find_freq(rcg->freq_tbl, rate); + f = qcom_find_freq(rcg->freq_tbl, rate); if (!f) return -EINVAL; diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h index ba0523cefd2..687e41f91d7 100644 --- a/drivers/clk/qcom/clk-rcg.h +++ b/drivers/clk/qcom/clk-rcg.h @@ -103,8 +103,9 @@ extern const struct clk_ops clk_rcg_bypass_ops; * struct clk_dyn_rcg - root clock generator with glitch free mux * * @mux_sel_bit: bit to switch glitch free mux - * @ns_reg: NS register + * @ns_reg: NS0 and NS1 register * @md_reg: MD0 and MD1 register + * @bank_reg: register to XOR @mux_sel_bit into to switch glitch free mux * @mn: mn counter (banked) * @s: source selector (banked) * @freq_tbl: frequency table @@ -113,8 +114,9 @@ extern const struct clk_ops clk_rcg_bypass_ops; * */ struct clk_dyn_rcg { - u32 ns_reg; + u32 ns_reg[2]; u32 md_reg[2]; + u32 bank_reg; u8 mux_sel_bit; diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index cd185d5cc67..cfa9eb4fe9c 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -24,6 +24,7 @@ #include <asm/div64.h> #include "clk-rcg.h" +#include "common.h" #define CMD_REG 0x0 #define CMD_UPDATE BIT(0) @@ -172,27 +173,13 @@ clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) return calc_rate(parent_rate, m, n, mode, hid_div); } -static const -struct freq_tbl *find_freq(const struct freq_tbl *f, unsigned long rate) -{ - if (!f) - return NULL; - - for (; f->freq; f++) - if (rate <= f->freq) - return f; - - /* Default to our fastest rate */ - return f - 1; -} - static long _freq_tbl_determine_rate(struct clk_hw *hw, const struct freq_tbl *f, unsigned long rate, unsigned long *p_rate, struct clk **p) { unsigned long clk_flags; - f = find_freq(f, rate); + f = qcom_find_freq(f, rate); if (!f) return -EINVAL; @@ -268,7 +255,7 @@ static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate) struct clk_rcg2 *rcg = to_clk_rcg2(hw); const struct freq_tbl *f; - f = find_freq(rcg->freq_tbl, rate); + f = qcom_find_freq(rcg->freq_tbl, rate); if (!f) return -EINVAL; diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index eeb3eea01f4..e20d947db3e 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -18,6 +18,7 @@ #include <linux/reset-controller.h> #include "common.h" +#include "clk-rcg.h" #include "clk-regmap.h" #include "reset.h" @@ -27,6 +28,21 @@ struct qcom_cc { struct clk *clks[]; }; +const +struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, unsigned long rate) +{ + if (!f) + return NULL; + + for (; f->freq; f++) + if (rate <= f->freq) + return f; + + /* Default to our fastest rate */ + return f - 1; +} +EXPORT_SYMBOL_GPL(qcom_find_freq); + struct regmap * qcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc) { diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h index 2765e9d3da9..f519322acdf 100644 --- a/drivers/clk/qcom/common.h +++ b/drivers/clk/qcom/common.h @@ -18,6 +18,7 @@ struct regmap_config; struct clk_regmap; struct qcom_reset_map; struct regmap; +struct freq_tbl; struct qcom_cc_desc { const struct regmap_config *config; @@ -27,6 +28,9 @@ struct qcom_cc_desc { size_t num_resets; }; +extern const struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, + unsigned long rate); + extern struct regmap *qcom_cc_map(struct platform_device *pdev, const struct qcom_cc_desc *desc); extern int qcom_cc_really_probe(struct platform_device *pdev, diff --git a/drivers/clk/qcom/gcc-ipq806x.c b/drivers/clk/qcom/gcc-ipq806x.c index 3b83b7dd78c..5cd62a709ac 100644 --- a/drivers/clk/qcom/gcc-ipq806x.c +++ b/drivers/clk/qcom/gcc-ipq806x.c @@ -32,6 +32,33 @@ #include "clk-branch.h" #include "reset.h" +static struct clk_pll pll0 = { + .l_reg = 0x30c4, + .m_reg = 0x30c8, + .n_reg = 0x30cc, + .config_reg = 0x30d4, + .mode_reg = 0x30c0, + .status_reg = 0x30d8, + .status_bit = 16, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pll0", + .parent_names = (const char *[]){ "pxo" }, + .num_parents = 1, + .ops = &clk_pll_ops, + }, +}; + +static struct clk_regmap pll0_vote = { + .enable_reg = 0x34c0, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "pll0_vote", + .parent_names = (const char *[]){ "pll0" }, + .num_parents = 1, + .ops = &clk_pll_vote_ops, + }, +}; + static struct clk_pll pll3 = { .l_reg = 0x3164, .m_reg = 0x3168, @@ -154,7 +181,7 @@ static const u8 gcc_pxo_pll8_pll0[] = { static const char *gcc_pxo_pll8_pll0_map[] = { "pxo", "pll8_vote", - "pll0", + "pll0_vote", }; static struct freq_tbl clk_tbl_gsbi_uart[] = { @@ -2133,6 +2160,8 @@ static struct clk_branch usb_fs1_h_clk = { }; static struct clk_regmap *gcc_ipq806x_clks[] = { + [PLL0] = &pll0.clkr, + [PLL0_VOTE] = &pll0_vote, [PLL3] = &pll3.clkr, [PLL8] = &pll8.clkr, [PLL8_VOTE] = &pll8_vote, diff --git a/drivers/clk/qcom/mmcc-apq8084.c b/drivers/clk/qcom/mmcc-apq8084.c index 751eea376a2..dab988ab8cf 100644 --- a/drivers/clk/qcom/mmcc-apq8084.c +++ b/drivers/clk/qcom/mmcc-apq8084.c @@ -3341,7 +3341,6 @@ static struct platform_driver mmcc_apq8084_driver = { .remove = mmcc_apq8084_remove, .driver = { .name = "mmcc-apq8084", - .owner = THIS_MODULE, .of_match_table = mmcc_apq8084_match_table, }, }; diff --git a/drivers/clk/qcom/mmcc-msm8960.c b/drivers/clk/qcom/mmcc-msm8960.c index 2e80a219b8e..e8b33bbc362 100644 --- a/drivers/clk/qcom/mmcc-msm8960.c +++ b/drivers/clk/qcom/mmcc-msm8960.c @@ -773,9 +773,11 @@ static struct freq_tbl clk_tbl_gfx2d[] = { }; static struct clk_dyn_rcg gfx2d0_src = { - .ns_reg = 0x0070, + .ns_reg[0] = 0x0070, + .ns_reg[1] = 0x0070, .md_reg[0] = 0x0064, .md_reg[1] = 0x0068, + .bank_reg = 0x0060, .mn[0] = { .mnctr_en_bit = 8, .mnctr_reset_bit = 25, @@ -831,9 +833,11 @@ static struct clk_branch gfx2d0_clk = { }; static struct clk_dyn_rcg gfx2d1_src = { - .ns_reg = 0x007c, + .ns_reg[0] = 0x007c, + .ns_reg[1] = 0x007c, .md_reg[0] = 0x0078, .md_reg[1] = 0x006c, + .bank_reg = 0x0074, .mn[0] = { .mnctr_en_bit = 8, .mnctr_reset_bit = 25, @@ -930,9 +934,11 @@ static struct freq_tbl clk_tbl_gfx3d_8064[] = { }; static struct clk_dyn_rcg gfx3d_src = { - .ns_reg = 0x008c, + .ns_reg[0] = 0x008c, + .ns_reg[1] = 0x008c, .md_reg[0] = 0x0084, .md_reg[1] = 0x0088, + .bank_reg = 0x0080, .mn[0] = { .mnctr_en_bit = 8, .mnctr_reset_bit = 25, @@ -1006,9 +1012,11 @@ static struct freq_tbl clk_tbl_vcap[] = { }; static struct clk_dyn_rcg vcap_src = { - .ns_reg = 0x021c, + .ns_reg[0] = 0x021c, + .ns_reg[1] = 0x021c, .md_reg[0] = 0x01ec, .md_reg[1] = 0x0218, + .bank_reg = 0x0178, .mn[0] = { .mnctr_en_bit = 8, .mnctr_reset_bit = 23, @@ -1211,9 +1219,11 @@ static struct freq_tbl clk_tbl_mdp[] = { }; static struct clk_dyn_rcg mdp_src = { - .ns_reg = 0x00d0, + .ns_reg[0] = 0x00d0, + .ns_reg[1] = 0x00d0, .md_reg[0] = 0x00c4, .md_reg[1] = 0x00c8, + .bank_reg = 0x00c0, .mn[0] = { .mnctr_en_bit = 8, .mnctr_reset_bit = 31, @@ -1318,7 +1328,9 @@ static struct freq_tbl clk_tbl_rot[] = { }; static struct clk_dyn_rcg rot_src = { - .ns_reg = 0x00e8, + .ns_reg[0] = 0x00e8, + .ns_reg[1] = 0x00e8, + .bank_reg = 0x00e8, .p[0] = { .pre_div_shift = 22, .pre_div_width = 4, @@ -1542,9 +1554,11 @@ static struct freq_tbl clk_tbl_vcodec[] = { }; static struct clk_dyn_rcg vcodec_src = { - .ns_reg = 0x0100, + .ns_reg[0] = 0x0100, + .ns_reg[1] = 0x0100, .md_reg[0] = 0x00fc, .md_reg[1] = 0x0128, + .bank_reg = 0x00f8, .mn[0] = { .mnctr_en_bit = 5, .mnctr_reset_bit = 31, @@ -2679,7 +2693,6 @@ static struct platform_driver mmcc_msm8960_driver = { .remove = mmcc_msm8960_remove, .driver = { .name = "mmcc-msm8960", - .owner = THIS_MODULE, .of_match_table = mmcc_msm8960_match_table, }, }; diff --git a/drivers/clk/qcom/mmcc-msm8974.c b/drivers/clk/qcom/mmcc-msm8974.c index bc8f519c47a..be94c54a9a4 100644 --- a/drivers/clk/qcom/mmcc-msm8974.c +++ b/drivers/clk/qcom/mmcc-msm8974.c @@ -2570,7 +2570,6 @@ static struct platform_driver mmcc_msm8974_driver = { .remove = mmcc_msm8974_remove, .driver = { .name = "mmcc-msm8974", - .owner = THIS_MODULE, .of_match_table = mmcc_msm8974_match_table, }, }; diff --git a/drivers/clk/rockchip/Makefile b/drivers/clk/rockchip/Makefile index ee6b077381e..bd8514d6363 100644 --- a/drivers/clk/rockchip/Makefile +++ b/drivers/clk/rockchip/Makefile @@ -5,6 +5,7 @@ obj-y += clk-rockchip.o obj-y += clk.o obj-y += clk-pll.o +obj-y += clk-cpu.o obj-$(CONFIG_RESET_CONTROLLER) += softrst.o obj-y += clk-rk3188.o diff --git a/drivers/clk/rockchip/clk-cpu.c b/drivers/clk/rockchip/clk-cpu.c new file mode 100644 index 00000000000..75c8c45ef72 --- /dev/null +++ b/drivers/clk/rockchip/clk-cpu.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2014 MundoReader S.L. + * Author: Heiko Stuebner <heiko@sntech.de> + * + * based on clk/samsung/clk-cpu.c + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Author: Thomas Abraham <thomas.ab@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs. + * The CPU clock is typically derived from a hierarchy of clock + * blocks which includes mux and divider blocks. There are a number of other + * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI + * clock for CPU domain. The rates of these auxiliary clocks are related to the + * CPU clock rate and this relation is usually specified in the hardware manual + * of the SoC or supplied after the SoC characterization. + * + * The below implementation of the CPU clock allows the rate changes of the CPU + * clock and the corresponding rate changes of the auxillary clocks of the CPU + * domain. The platform clock driver provides a clock register configuration + * for each configurable rate which is then used to program the clock hardware + * registers to acheive a fast co-oridinated rate change for all the CPU domain + * clocks. + * + * On a rate change request for the CPU clock, the rate change is propagated + * upto the PLL supplying the clock to the CPU domain clock blocks. While the + * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an + * alternate clock source. If required, the alternate clock source is divided + * down in order to keep the output clock rate within the previous OPP limits. + */ + +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk-provider.h> +#include "clk.h" + +/** + * struct rockchip_cpuclk: information about clock supplied to a CPU core. + * @hw: handle between ccf and cpu clock. + * @alt_parent: alternate parent clock to use when switching the speed + * of the primary parent clock. + * @reg_base: base register for cpu-clock values. + * @clk_nb: clock notifier registered for changes in clock speed of the + * primary parent clock. + * @rate_count: number of rates in the rate_table + * @rate_table: pll-rates and their associated dividers + * @reg_data: cpu-specific register settings + * @lock: clock lock + */ +struct rockchip_cpuclk { + struct clk_hw hw; + + struct clk_mux cpu_mux; + const struct clk_ops *cpu_mux_ops; + + struct clk *alt_parent; + void __iomem *reg_base; + struct notifier_block clk_nb; + unsigned int rate_count; + struct rockchip_cpuclk_rate_table *rate_table; + const struct rockchip_cpuclk_reg_data *reg_data; + spinlock_t *lock; +}; + +#define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw) +#define to_rockchip_cpuclk_nb(nb) \ + container_of(nb, struct rockchip_cpuclk, clk_nb) + +static const struct rockchip_cpuclk_rate_table *rockchip_get_cpuclk_settings( + struct rockchip_cpuclk *cpuclk, unsigned long rate) +{ + const struct rockchip_cpuclk_rate_table *rate_table = + cpuclk->rate_table; + int i; + + for (i = 0; i < cpuclk->rate_count; i++) { + if (rate == rate_table[i].prate) + return &rate_table[i]; + } + + return NULL; +} + +static unsigned long rockchip_cpuclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw); + const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; + u32 clksel0 = readl_relaxed(cpuclk->reg_base + reg_data->core_reg); + + clksel0 >>= reg_data->div_core_shift; + clksel0 &= reg_data->div_core_mask; + return parent_rate / (clksel0 + 1); +} + +static const struct clk_ops rockchip_cpuclk_ops = { + .recalc_rate = rockchip_cpuclk_recalc_rate, +}; + +static void rockchip_cpuclk_set_dividers(struct rockchip_cpuclk *cpuclk, + const struct rockchip_cpuclk_rate_table *rate) +{ + int i; + + /* alternate parent is active now. set the dividers */ + for (i = 0; i < ARRAY_SIZE(rate->divs); i++) { + const struct rockchip_cpuclk_clksel *clksel = &rate->divs[i]; + + if (!clksel->reg) + continue; + + pr_debug("%s: setting reg 0x%x to 0x%x\n", + __func__, clksel->reg, clksel->val); + writel(clksel->val , cpuclk->reg_base + clksel->reg); + } +} + +static int rockchip_cpuclk_pre_rate_change(struct rockchip_cpuclk *cpuclk, + struct clk_notifier_data *ndata) +{ + const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; + unsigned long alt_prate, alt_div; + + alt_prate = clk_get_rate(cpuclk->alt_parent); + + spin_lock(cpuclk->lock); + + /* + * If the old parent clock speed is less than the clock speed + * of the alternate parent, then it should be ensured that at no point + * the armclk speed is more than the old_rate until the dividers are + * set. + */ + if (alt_prate > ndata->old_rate) { + /* calculate dividers */ + alt_div = DIV_ROUND_UP(alt_prate, ndata->old_rate) - 1; + if (alt_div > reg_data->div_core_mask) { + pr_warn("%s: limiting alt-divider %lu to %d\n", + __func__, alt_div, reg_data->div_core_mask); + alt_div = reg_data->div_core_mask; + } + + /* + * Change parents and add dividers in a single transaction. + * + * NOTE: we do this in a single transaction so we're never + * dividing the primary parent by the extra dividers that were + * needed for the alt. + */ + pr_debug("%s: setting div %lu as alt-rate %lu > old-rate %lu\n", + __func__, alt_div, alt_prate, ndata->old_rate); + + writel(HIWORD_UPDATE(alt_div, reg_data->div_core_mask, + reg_data->div_core_shift) | + HIWORD_UPDATE(1, 1, reg_data->mux_core_shift), + cpuclk->reg_base + reg_data->core_reg); + } else { + /* select alternate parent */ + writel(HIWORD_UPDATE(1, 1, reg_data->mux_core_shift), + cpuclk->reg_base + reg_data->core_reg); + } + + spin_unlock(cpuclk->lock); + return 0; +} + +static int rockchip_cpuclk_post_rate_change(struct rockchip_cpuclk *cpuclk, + struct clk_notifier_data *ndata) +{ + const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data; + const struct rockchip_cpuclk_rate_table *rate; + + rate = rockchip_get_cpuclk_settings(cpuclk, ndata->new_rate); + if (!rate) { + pr_err("%s: Invalid rate : %lu for cpuclk\n", + __func__, ndata->new_rate); + return -EINVAL; + } + + spin_lock(cpuclk->lock); + + if (ndata->old_rate < ndata->new_rate) + rockchip_cpuclk_set_dividers(cpuclk, rate); + + /* + * post-rate change event, re-mux to primary parent and remove dividers. + * + * NOTE: we do this in a single transaction so we're never dividing the + * primary parent by the extra dividers that were needed for the alt. + */ + + writel(HIWORD_UPDATE(0, reg_data->div_core_mask, + reg_data->div_core_shift) | + HIWORD_UPDATE(0, 1, reg_data->mux_core_shift), + cpuclk->reg_base + reg_data->core_reg); + + if (ndata->old_rate > ndata->new_rate) + rockchip_cpuclk_set_dividers(cpuclk, rate); + + spin_unlock(cpuclk->lock); + return 0; +} + +/* + * This clock notifier is called when the frequency of the parent clock + * of cpuclk is to be changed. This notifier handles the setting up all + * the divider clocks, remux to temporary parent and handling the safe + * frequency levels when using temporary parent. + */ +static int rockchip_cpuclk_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_nb(nb); + int ret = 0; + + pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n", + __func__, event, ndata->old_rate, ndata->new_rate); + if (event == PRE_RATE_CHANGE) + ret = rockchip_cpuclk_pre_rate_change(cpuclk, ndata); + else if (event == POST_RATE_CHANGE) + ret = rockchip_cpuclk_post_rate_change(cpuclk, ndata); + + return notifier_from_errno(ret); +} + +struct clk *rockchip_clk_register_cpuclk(const char *name, + const char **parent_names, u8 num_parents, + const struct rockchip_cpuclk_reg_data *reg_data, + const struct rockchip_cpuclk_rate_table *rates, + int nrates, void __iomem *reg_base, spinlock_t *lock) +{ + struct rockchip_cpuclk *cpuclk; + struct clk_init_data init; + struct clk *clk, *cclk; + int ret; + + if (num_parents != 2) { + pr_err("%s: needs two parent clocks\n", __func__); + return ERR_PTR(-EINVAL); + } + + cpuclk = kzalloc(sizeof(*cpuclk), GFP_KERNEL); + if (!cpuclk) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.parent_names = &parent_names[0]; + init.num_parents = 1; + init.ops = &rockchip_cpuclk_ops; + + /* only allow rate changes when we have a rate table */ + init.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0; + + /* disallow automatic parent changes by ccf */ + init.flags |= CLK_SET_RATE_NO_REPARENT; + + init.flags |= CLK_GET_RATE_NOCACHE; + + cpuclk->reg_base = reg_base; + cpuclk->lock = lock; + cpuclk->reg_data = reg_data; + cpuclk->clk_nb.notifier_call = rockchip_cpuclk_notifier_cb; + cpuclk->hw.init = &init; + + cpuclk->alt_parent = __clk_lookup(parent_names[1]); + if (!cpuclk->alt_parent) { + pr_err("%s: could not lookup alternate parent\n", + __func__); + ret = -EINVAL; + goto free_cpuclk; + } + + ret = clk_prepare_enable(cpuclk->alt_parent); + if (ret) { + pr_err("%s: could not enable alternate parent\n", + __func__); + goto free_cpuclk; + } + + clk = __clk_lookup(parent_names[0]); + if (!clk) { + pr_err("%s: could not lookup parent clock %s\n", + __func__, parent_names[0]); + ret = -EINVAL; + goto free_cpuclk; + } + + ret = clk_notifier_register(clk, &cpuclk->clk_nb); + if (ret) { + pr_err("%s: failed to register clock notifier for %s\n", + __func__, name); + goto free_cpuclk; + } + + if (nrates > 0) { + cpuclk->rate_count = nrates; + cpuclk->rate_table = kmemdup(rates, + sizeof(*rates) * nrates, + GFP_KERNEL); + if (!cpuclk->rate_table) { + pr_err("%s: could not allocate memory for cpuclk rates\n", + __func__); + ret = -ENOMEM; + goto unregister_notifier; + } + } + + cclk = clk_register(NULL, &cpuclk->hw); + if (IS_ERR(clk)) { + pr_err("%s: could not register cpuclk %s\n", __func__, name); + ret = PTR_ERR(clk); + goto free_rate_table; + } + + return cclk; + +free_rate_table: + kfree(cpuclk->rate_table); +unregister_notifier: + clk_notifier_unregister(clk, &cpuclk->clk_nb); +free_cpuclk: + kfree(cpuclk); + return ERR_PTR(ret); +} diff --git a/drivers/clk/rockchip/clk-pll.c b/drivers/clk/rockchip/clk-pll.c index f2a1c7abf4d..a3e886a3848 100644 --- a/drivers/clk/rockchip/clk-pll.c +++ b/drivers/clk/rockchip/clk-pll.c @@ -34,7 +34,6 @@ struct rockchip_clk_pll { const struct clk_ops *pll_mux_ops; struct notifier_block clk_nb; - bool rate_change_remuxed; void __iomem *reg_base; int lock_offset; @@ -109,38 +108,6 @@ static int rockchip_pll_wait_lock(struct rockchip_clk_pll *pll) } /** - * Set pll mux when changing the pll rate. - * This makes sure to move the pll mux away from the actual pll before - * changing its rate and back to the original parent after the change. - */ -static int rockchip_pll_notifier_cb(struct notifier_block *nb, - unsigned long event, void *data) -{ - struct rockchip_clk_pll *pll = to_rockchip_clk_pll_nb(nb); - struct clk_mux *pll_mux = &pll->pll_mux; - const struct clk_ops *pll_mux_ops = pll->pll_mux_ops; - int cur_parent; - - switch (event) { - case PRE_RATE_CHANGE: - cur_parent = pll_mux_ops->get_parent(&pll_mux->hw); - if (cur_parent == PLL_MODE_NORM) { - pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW); - pll->rate_change_remuxed = 1; - } - break; - case POST_RATE_CHANGE: - if (pll->rate_change_remuxed) { - pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM); - pll->rate_change_remuxed = 0; - } - break; - } - - return NOTIFY_OK; -} - -/** * PLL used in RK3066, RK3188 and RK3288 */ @@ -194,6 +161,10 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate, const struct rockchip_pll_rate_table *rate; unsigned long old_rate = rockchip_rk3066_pll_recalc_rate(hw, prate); struct regmap *grf = rockchip_clk_get_grf(); + struct clk_mux *pll_mux = &pll->pll_mux; + const struct clk_ops *pll_mux_ops = pll->pll_mux_ops; + int rate_change_remuxed = 0; + int cur_parent; int ret; if (IS_ERR(grf)) { @@ -216,6 +187,12 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate, pr_debug("%s: rate settings for %lu (nr, no, nf): (%d, %d, %d)\n", __func__, rate->rate, rate->nr, rate->no, rate->nf); + cur_parent = pll_mux_ops->get_parent(&pll_mux->hw); + if (cur_parent == PLL_MODE_NORM) { + pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_SLOW); + rate_change_remuxed = 1; + } + /* enter reset mode */ writel(HIWORD_UPDATE(RK3066_PLLCON3_RESET, RK3066_PLLCON3_RESET, 0), pll->reg_base + RK3066_PLLCON(3)); @@ -247,6 +224,9 @@ static int rockchip_rk3066_pll_set_rate(struct clk_hw *hw, unsigned long drate, rockchip_rk3066_pll_set_rate(hw, old_rate, prate); } + if (rate_change_remuxed) + pll_mux_ops->set_parent(&pll_mux->hw, PLL_MODE_NORM); + return ret; } @@ -310,7 +290,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type, struct clk_mux *pll_mux; struct clk *pll_clk, *mux_clk; char pll_name[20]; - int ret; if (num_parents != 2) { pr_err("%s: needs two parent clocks\n", __func__); @@ -367,7 +346,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type, pll->lock_offset = grf_lock_offset; pll->lock_shift = lock_shift; pll->lock = lock; - pll->clk_nb.notifier_call = rockchip_pll_notifier_cb; pll_clk = clk_register(NULL, &pll->hw); if (IS_ERR(pll_clk)) { @@ -377,14 +355,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type, goto err_pll; } - ret = clk_notifier_register(pll_clk, &pll->clk_nb); - if (ret) { - pr_err("%s: failed to register clock notifier for %s : %d\n", - __func__, name, ret); - mux_clk = ERR_PTR(ret); - goto err_pll_notifier; - } - /* create the mux on top of the real pll */ pll->pll_mux_ops = &clk_mux_ops; pll_mux = &pll->pll_mux; @@ -417,13 +387,6 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type, return mux_clk; err_mux: - ret = clk_notifier_unregister(pll_clk, &pll->clk_nb); - if (ret) { - pr_err("%s: could not unregister clock notifier in error path : %d\n", - __func__, ret); - return mux_clk; - } -err_pll_notifier: clk_unregister(pll_clk); err_pll: kfree(pll); diff --git a/drivers/clk/rockchip/clk-rk3188.c b/drivers/clk/rockchip/clk-rk3188.c index a83a6d8d0fb..beed49c7912 100644 --- a/drivers/clk/rockchip/clk-rk3188.c +++ b/drivers/clk/rockchip/clk-rk3188.c @@ -19,6 +19,7 @@ #include <dt-bindings/clock/rk3188-cru-common.h> #include "clk.h" +#define RK3066_GRF_SOC_STATUS 0x15c #define RK3188_GRF_SOC_STATUS 0xac enum rk3188_plls { @@ -100,6 +101,98 @@ struct rockchip_pll_rate_table rk3188_pll_rates[] = { { /* sentinel */ }, }; +#define RK3066_DIV_CORE_PERIPH_MASK 0x3 +#define RK3066_DIV_CORE_PERIPH_SHIFT 6 +#define RK3066_DIV_ACLK_CORE_MASK 0x7 +#define RK3066_DIV_ACLK_CORE_SHIFT 0 +#define RK3066_DIV_ACLK_HCLK_MASK 0x3 +#define RK3066_DIV_ACLK_HCLK_SHIFT 8 +#define RK3066_DIV_ACLK_PCLK_MASK 0x3 +#define RK3066_DIV_ACLK_PCLK_SHIFT 12 +#define RK3066_DIV_AHB2APB_MASK 0x3 +#define RK3066_DIV_AHB2APB_SHIFT 14 + +#define RK3066_CLKSEL0(_core_peri) \ + { \ + .reg = RK2928_CLKSEL_CON(0), \ + .val = HIWORD_UPDATE(_core_peri, RK3066_DIV_CORE_PERIPH_MASK, \ + RK3066_DIV_CORE_PERIPH_SHIFT) \ + } +#define RK3066_CLKSEL1(_aclk_core, _aclk_hclk, _aclk_pclk, _ahb2apb) \ + { \ + .reg = RK2928_CLKSEL_CON(1), \ + .val = HIWORD_UPDATE(_aclk_core, RK3066_DIV_ACLK_CORE_MASK, \ + RK3066_DIV_ACLK_CORE_SHIFT) | \ + HIWORD_UPDATE(_aclk_hclk, RK3066_DIV_ACLK_HCLK_MASK, \ + RK3066_DIV_ACLK_HCLK_SHIFT) | \ + HIWORD_UPDATE(_aclk_pclk, RK3066_DIV_ACLK_PCLK_MASK, \ + RK3066_DIV_ACLK_PCLK_SHIFT) | \ + HIWORD_UPDATE(_ahb2apb, RK3066_DIV_AHB2APB_MASK, \ + RK3066_DIV_AHB2APB_SHIFT), \ + } + +#define RK3066_CPUCLK_RATE(_prate, _core_peri, _acore, _ahclk, _apclk, _h2p) \ + { \ + .prate = _prate, \ + .divs = { \ + RK3066_CLKSEL0(_core_peri), \ + RK3066_CLKSEL1(_acore, _ahclk, _apclk, _h2p), \ + }, \ + } + +static struct rockchip_cpuclk_rate_table rk3066_cpuclk_rates[] __initdata = { + RK3066_CPUCLK_RATE(1416000000, 2, 3, 1, 2, 1), + RK3066_CPUCLK_RATE(1200000000, 2, 3, 1, 2, 1), + RK3066_CPUCLK_RATE(1008000000, 2, 2, 1, 2, 1), + RK3066_CPUCLK_RATE( 816000000, 2, 2, 1, 2, 1), + RK3066_CPUCLK_RATE( 600000000, 1, 2, 1, 2, 1), + RK3066_CPUCLK_RATE( 504000000, 1, 1, 1, 2, 1), + RK3066_CPUCLK_RATE( 312000000, 0, 1, 1, 1, 0), +}; + +static const struct rockchip_cpuclk_reg_data rk3066_cpuclk_data = { + .core_reg = RK2928_CLKSEL_CON(0), + .div_core_shift = 0, + .div_core_mask = 0x1f, + .mux_core_shift = 8, +}; + +#define RK3188_DIV_ACLK_CORE_MASK 0x7 +#define RK3188_DIV_ACLK_CORE_SHIFT 3 + +#define RK3188_CLKSEL1(_aclk_core) \ + { \ + .reg = RK2928_CLKSEL_CON(1), \ + .val = HIWORD_UPDATE(_aclk_core, RK3188_DIV_ACLK_CORE_MASK,\ + RK3188_DIV_ACLK_CORE_SHIFT) \ + } +#define RK3188_CPUCLK_RATE(_prate, _core_peri, _aclk_core) \ + { \ + .prate = _prate, \ + .divs = { \ + RK3066_CLKSEL0(_core_peri), \ + RK3188_CLKSEL1(_aclk_core), \ + }, \ + } + +static struct rockchip_cpuclk_rate_table rk3188_cpuclk_rates[] __initdata = { + RK3188_CPUCLK_RATE(1608000000, 2, 3), + RK3188_CPUCLK_RATE(1416000000, 2, 3), + RK3188_CPUCLK_RATE(1200000000, 2, 3), + RK3188_CPUCLK_RATE(1008000000, 2, 3), + RK3188_CPUCLK_RATE( 816000000, 2, 3), + RK3188_CPUCLK_RATE( 600000000, 1, 3), + RK3188_CPUCLK_RATE( 504000000, 1, 3), + RK3188_CPUCLK_RATE( 312000000, 0, 1), +}; + +static const struct rockchip_cpuclk_reg_data rk3188_cpuclk_data = { + .core_reg = RK2928_CLKSEL_CON(0), + .div_core_shift = 9, + .div_core_mask = 0x1f, + .mux_core_shift = 8, +}; + PNAME(mux_pll_p) = { "xin24m", "xin32k" }; PNAME(mux_armclk_p) = { "apll", "gpll_armclk" }; PNAME(mux_ddrphy_p) = { "dpll", "gpll_ddr" }; @@ -173,17 +266,10 @@ static struct rockchip_clk_branch common_clk_branches[] __initdata = { GATE(0, "aclk_cpu", "aclk_cpu_pre", 0, RK2928_CLKGATE_CON(0), 3, GFLAGS), - DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0, - RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO), GATE(0, "atclk_cpu", "pclk_cpu_pre", 0, RK2928_CLKGATE_CON(0), 6, GFLAGS), GATE(0, "pclk_cpu", "pclk_cpu_pre", 0, RK2928_CLKGATE_CON(0), 5, GFLAGS), - DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0, - RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO), - COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0, - RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO, - RK2928_CLKGATE_CON(4), 9, GFLAGS), GATE(0, "hclk_cpu", "hclk_cpu_pre", 0, RK2928_CLKGATE_CON(0), 4, GFLAGS), @@ -412,10 +498,18 @@ static struct clk_div_table div_aclk_cpu_t[] = { }; static struct rockchip_clk_branch rk3066a_clk_branches[] __initdata = { - COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0, - RK2928_CLKSEL_CON(0), 8, 1, MFLAGS, 0, 5, DFLAGS), DIVTBL(0, "aclk_cpu_pre", "armclk", 0, - RK2928_CLKSEL_CON(1), 0, 3, DFLAGS, div_aclk_cpu_t), + RK2928_CLKSEL_CON(1), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, div_aclk_cpu_t), + DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO + | CLK_DIVIDER_READ_ONLY), + DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO + | CLK_DIVIDER_READ_ONLY), + COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO + | CLK_DIVIDER_READ_ONLY, + RK2928_CLKGATE_CON(4), 9, GFLAGS), GATE(CORE_L2C, "core_l2c", "aclk_cpu", 0, RK2928_CLKGATE_CON(9), 4, GFLAGS), @@ -524,8 +618,6 @@ PNAME(mux_hsicphy_p) = { "sclk_otgphy0", "sclk_otgphy1", "gpll", "cpll" }; static struct rockchip_clk_branch rk3188_clk_branches[] __initdata = { - COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0, - RK2928_CLKSEL_CON(0), 8, 1, MFLAGS, 9, 5, DFLAGS), COMPOSITE_NOMUX_DIVTBL(0, "aclk_core", "armclk", 0, RK2928_CLKSEL_CON(1), 3, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, div_rk3188_aclk_core_t, RK2928_CLKGATE_CON(0), 7, GFLAGS), @@ -533,6 +625,13 @@ static struct rockchip_clk_branch rk3188_clk_branches[] __initdata = { /* do not source aclk_cpu_pre from the apll, to keep complexity down */ COMPOSITE_NOGATE(0, "aclk_cpu_pre", mux_aclk_cpu_p, CLK_SET_RATE_NO_REPARENT, RK2928_CLKSEL_CON(0), 5, 1, MFLAGS, 0, 5, DFLAGS), + DIV(0, "pclk_cpu_pre", "aclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO), + DIV(0, "hclk_cpu_pre", "aclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO), + COMPOSITE_NOMUX(0, "hclk_ahb2apb", "hclk_cpu_pre", 0, + RK2928_CLKSEL_CON(1), 14, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO, + RK2928_CLKGATE_CON(4), 9, GFLAGS), GATE(CORE_L2C, "core_l2c", "armclk", 0, RK2928_CLKGATE_CON(9), 4, GFLAGS), @@ -599,6 +698,12 @@ static struct rockchip_clk_branch rk3188_clk_branches[] __initdata = { GATE(ACLK_GPS, "aclk_gps", "aclk_peri", 0, RK2928_CLKGATE_CON(8), 13, GFLAGS), }; +static const char *rk3188_critical_clocks[] __initconst = { + "aclk_cpu", + "aclk_peri", + "hclk_peri", +}; + static void __init rk3188_common_clk_init(struct device_node *np) { void __iomem *reg_base; @@ -623,29 +728,65 @@ static void __init rk3188_common_clk_init(struct device_node *np) pr_warn("%s: could not register clock usb480m: %ld\n", __func__, PTR_ERR(clk)); - rockchip_clk_register_plls(rk3188_pll_clks, - ARRAY_SIZE(rk3188_pll_clks), - RK3188_GRF_SOC_STATUS); rockchip_clk_register_branches(common_clk_branches, ARRAY_SIZE(common_clk_branches)); + rockchip_clk_protect_critical(rk3188_critical_clocks, + ARRAY_SIZE(rk3188_critical_clocks)); rockchip_register_softrst(np, 9, reg_base + RK2928_SOFTRST_CON(0), ROCKCHIP_SOFTRST_HIWORD_MASK); + + rockchip_register_restart_notifier(RK2928_GLB_SRST_FST); } static void __init rk3066a_clk_init(struct device_node *np) { rk3188_common_clk_init(np); + rockchip_clk_register_plls(rk3188_pll_clks, + ARRAY_SIZE(rk3188_pll_clks), + RK3066_GRF_SOC_STATUS); rockchip_clk_register_branches(rk3066a_clk_branches, ARRAY_SIZE(rk3066a_clk_branches)); + rockchip_clk_register_armclk(ARMCLK, "armclk", + mux_armclk_p, ARRAY_SIZE(mux_armclk_p), + &rk3066_cpuclk_data, rk3066_cpuclk_rates, + ARRAY_SIZE(rk3066_cpuclk_rates)); } CLK_OF_DECLARE(rk3066a_cru, "rockchip,rk3066a-cru", rk3066a_clk_init); static void __init rk3188a_clk_init(struct device_node *np) { + struct clk *clk1, *clk2; + unsigned long rate; + int ret; + rk3188_common_clk_init(np); + rockchip_clk_register_plls(rk3188_pll_clks, + ARRAY_SIZE(rk3188_pll_clks), + RK3188_GRF_SOC_STATUS); rockchip_clk_register_branches(rk3188_clk_branches, ARRAY_SIZE(rk3188_clk_branches)); + rockchip_clk_register_armclk(ARMCLK, "armclk", + mux_armclk_p, ARRAY_SIZE(mux_armclk_p), + &rk3188_cpuclk_data, rk3188_cpuclk_rates, + ARRAY_SIZE(rk3188_cpuclk_rates)); + + /* reparent aclk_cpu_pre from apll */ + clk1 = __clk_lookup("aclk_cpu_pre"); + clk2 = __clk_lookup("gpll"); + if (clk1 && clk2) { + rate = clk_get_rate(clk1); + + ret = clk_set_parent(clk1, clk2); + if (ret < 0) + pr_warn("%s: could not reparent aclk_cpu_pre to gpll\n", + __func__); + + clk_set_rate(clk1, rate); + } else { + pr_warn("%s: missing clocks to reparent aclk_cpu_pre to gpll\n", + __func__); + } } CLK_OF_DECLARE(rk3188a_cru, "rockchip,rk3188a-cru", rk3188a_clk_init); diff --git a/drivers/clk/rockchip/clk-rk3288.c b/drivers/clk/rockchip/clk-rk3288.c index b22a2d2f21e..23278291da4 100644 --- a/drivers/clk/rockchip/clk-rk3288.c +++ b/drivers/clk/rockchip/clk-rk3288.c @@ -20,7 +20,7 @@ #include "clk.h" #define RK3288_GRF_SOC_CON(x) (0x244 + x * 4) -#define RK3288_GRF_SOC_STATUS 0x280 +#define RK3288_GRF_SOC_STATUS1 0x284 enum rk3288_plls { apll, dpll, cpll, gpll, npll, @@ -101,6 +101,70 @@ struct rockchip_pll_rate_table rk3288_pll_rates[] = { { /* sentinel */ }, }; +#define RK3288_DIV_ACLK_CORE_M0_MASK 0xf +#define RK3288_DIV_ACLK_CORE_M0_SHIFT 0 +#define RK3288_DIV_ACLK_CORE_MP_MASK 0xf +#define RK3288_DIV_ACLK_CORE_MP_SHIFT 4 +#define RK3288_DIV_L2RAM_MASK 0x7 +#define RK3288_DIV_L2RAM_SHIFT 0 +#define RK3288_DIV_ATCLK_MASK 0x1f +#define RK3288_DIV_ATCLK_SHIFT 4 +#define RK3288_DIV_PCLK_DBGPRE_MASK 0x1f +#define RK3288_DIV_PCLK_DBGPRE_SHIFT 9 + +#define RK3288_CLKSEL0(_core_m0, _core_mp) \ + { \ + .reg = RK3288_CLKSEL_CON(0), \ + .val = HIWORD_UPDATE(_core_m0, RK3288_DIV_ACLK_CORE_M0_MASK, \ + RK3288_DIV_ACLK_CORE_M0_SHIFT) | \ + HIWORD_UPDATE(_core_mp, RK3288_DIV_ACLK_CORE_MP_MASK, \ + RK3288_DIV_ACLK_CORE_MP_SHIFT), \ + } +#define RK3288_CLKSEL37(_l2ram, _atclk, _pclk_dbg_pre) \ + { \ + .reg = RK3288_CLKSEL_CON(37), \ + .val = HIWORD_UPDATE(_l2ram, RK3288_DIV_L2RAM_MASK, \ + RK3288_DIV_L2RAM_SHIFT) | \ + HIWORD_UPDATE(_atclk, RK3288_DIV_ATCLK_MASK, \ + RK3288_DIV_ATCLK_SHIFT) | \ + HIWORD_UPDATE(_pclk_dbg_pre, \ + RK3288_DIV_PCLK_DBGPRE_MASK, \ + RK3288_DIV_PCLK_DBGPRE_SHIFT), \ + } + +#define RK3288_CPUCLK_RATE(_prate, _core_m0, _core_mp, _l2ram, _atclk, _pdbg) \ + { \ + .prate = _prate, \ + .divs = { \ + RK3288_CLKSEL0(_core_m0, _core_mp), \ + RK3288_CLKSEL37(_l2ram, _atclk, _pdbg), \ + }, \ + } + +static struct rockchip_cpuclk_rate_table rk3288_cpuclk_rates[] __initdata = { + RK3288_CPUCLK_RATE(1800000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1704000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1608000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1512000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1416000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1200000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE(1008000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 816000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 696000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 600000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 408000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 312000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 216000000, 2, 4, 2, 4, 4), + RK3288_CPUCLK_RATE( 126000000, 2, 4, 2, 4, 4), +}; + +static const struct rockchip_cpuclk_reg_data rk3288_cpuclk_data = { + .core_reg = RK3288_CLKSEL_CON(0), + .div_core_shift = 8, + .div_core_mask = 0x1f, + .mux_core_shift = 15, +}; + PNAME(mux_pll_p) = { "xin24m", "xin32k" }; PNAME(mux_armclk_p) = { "apll_core", "gpll_core" }; PNAME(mux_ddrphy_p) = { "dpll_ddr", "gpll_ddr" }; @@ -143,7 +207,7 @@ static struct rockchip_pll_clock rk3288_pll_clks[] __initdata = { [gpll] = PLL(pll_rk3066, PLL_GPLL, "gpll", mux_pll_p, 0, RK3288_PLL_CON(12), RK3288_MODE_CON, 12, 8, rk3288_pll_rates), [npll] = PLL(pll_rk3066, PLL_NPLL, "npll", mux_pll_p, 0, RK3288_PLL_CON(16), - RK3288_MODE_CON, 14, 9, NULL), + RK3288_MODE_CON, 14, 9, rk3288_pll_rates), }; static struct clk_div_table div_hclk_cpu_t[] = { @@ -166,35 +230,33 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { RK3288_CLKGATE_CON(0), 1, GFLAGS), GATE(0, "gpll_core", "gpll", 0, RK3288_CLKGATE_CON(0), 2, GFLAGS), - COMPOSITE_NOGATE(0, "armclk", mux_armclk_p, 0, - RK3288_CLKSEL_CON(0), 15, 1, MFLAGS, 8, 5, DFLAGS), COMPOSITE_NOMUX(0, "armcore0", "armclk", 0, - RK3288_CLKSEL_CON(36), 0, 3, DFLAGS, + RK3288_CLKSEL_CON(36), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 0, GFLAGS), COMPOSITE_NOMUX(0, "armcore1", "armclk", 0, - RK3288_CLKSEL_CON(36), 4, 3, DFLAGS, + RK3288_CLKSEL_CON(36), 4, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 1, GFLAGS), COMPOSITE_NOMUX(0, "armcore2", "armclk", 0, - RK3288_CLKSEL_CON(36), 8, 3, DFLAGS, + RK3288_CLKSEL_CON(36), 8, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 2, GFLAGS), COMPOSITE_NOMUX(0, "armcore3", "armclk", 0, - RK3288_CLKSEL_CON(36), 12, 3, DFLAGS, + RK3288_CLKSEL_CON(36), 12, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 3, GFLAGS), COMPOSITE_NOMUX(0, "l2ram", "armclk", 0, - RK3288_CLKSEL_CON(37), 0, 3, DFLAGS, + RK3288_CLKSEL_CON(37), 0, 3, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 4, GFLAGS), COMPOSITE_NOMUX(0, "aclk_core_m0", "armclk", 0, - RK3288_CLKSEL_CON(0), 0, 4, DFLAGS, + RK3288_CLKSEL_CON(0), 0, 4, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 5, GFLAGS), COMPOSITE_NOMUX(0, "aclk_core_mp", "armclk", 0, - RK3288_CLKSEL_CON(0), 4, 4, DFLAGS, + RK3288_CLKSEL_CON(0), 4, 4, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 6, GFLAGS), COMPOSITE_NOMUX(0, "atclk", "armclk", 0, - RK3288_CLKSEL_CON(37), 4, 5, DFLAGS, + RK3288_CLKSEL_CON(37), 4, 5, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 7, GFLAGS), COMPOSITE_NOMUX(0, "pclk_dbg_pre", "armclk", 0, - RK3288_CLKSEL_CON(37), 9, 5, DFLAGS, + RK3288_CLKSEL_CON(37), 9, 5, DFLAGS | CLK_DIVIDER_READ_ONLY, RK3288_CLKGATE_CON(12), 8, GFLAGS), GATE(0, "pclk_dbg", "pclk_dbg_pre", 0, RK3288_CLKGATE_CON(12), 9, GFLAGS), @@ -219,12 +281,12 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { RK3288_CLKSEL_CON(1), 15, 1, MFLAGS, 3, 5, DFLAGS), DIV(0, "aclk_cpu_pre", "aclk_cpu_src", 0, RK3288_CLKSEL_CON(1), 0, 3, DFLAGS), - GATE(0, "aclk_cpu", "aclk_cpu_pre", 0, + GATE(ACLK_CPU, "aclk_cpu", "aclk_cpu_pre", 0, RK3288_CLKGATE_CON(0), 3, GFLAGS), - COMPOSITE_NOMUX(0, "pclk_cpu", "aclk_cpu_pre", 0, + COMPOSITE_NOMUX(PCLK_CPU, "pclk_cpu", "aclk_cpu_pre", 0, RK3288_CLKSEL_CON(1), 12, 3, DFLAGS, RK3288_CLKGATE_CON(0), 5, GFLAGS), - COMPOSITE_NOMUX_DIVTBL(0, "hclk_cpu", "aclk_cpu_pre", 0, + COMPOSITE_NOMUX_DIVTBL(HCLK_CPU, "hclk_cpu", "aclk_cpu_pre", 0, RK3288_CLKSEL_CON(1), 8, 2, DFLAGS, div_hclk_cpu_t, RK3288_CLKGATE_CON(0), 4, GFLAGS), GATE(0, "c2c_host", "aclk_cpu_src", 0, @@ -238,15 +300,15 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { COMPOSITE(0, "i2s_src", mux_pll_src_cpll_gpll_p, 0, RK3288_CLKSEL_CON(4), 15, 1, MFLAGS, 0, 7, DFLAGS, RK3288_CLKGATE_CON(4), 1, GFLAGS), - COMPOSITE_FRAC(0, "i2s_frac", "i2s_src", 0, + COMPOSITE_FRAC(0, "i2s_frac", "i2s_src", CLK_SET_RATE_PARENT, RK3288_CLKSEL_CON(8), 0, RK3288_CLKGATE_CON(4), 2, GFLAGS), - MUX(0, "i2s_pre", mux_i2s_pre_p, 0, + MUX(0, "i2s_pre", mux_i2s_pre_p, CLK_SET_RATE_PARENT, RK3288_CLKSEL_CON(4), 8, 2, MFLAGS), - COMPOSITE_NODIV(0, "i2s0_clkout", mux_i2s_clkout_p, 0, + COMPOSITE_NODIV(0, "i2s0_clkout", mux_i2s_clkout_p, CLK_SET_RATE_PARENT, RK3288_CLKSEL_CON(4), 12, 1, MFLAGS, RK3288_CLKGATE_CON(4), 0, GFLAGS), - GATE(SCLK_I2S0, "sclk_i2s0", "i2s_pre", 0, + GATE(SCLK_I2S0, "sclk_i2s0", "i2s_pre", CLK_SET_RATE_PARENT, RK3288_CLKGATE_CON(4), 3, GFLAGS), MUX(0, "spdif_src", mux_pll_src_cpll_gpll_p, 0, @@ -296,6 +358,20 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { COMPOSITE(0, "aclk_vdpu", mux_pll_src_cpll_gpll_usb480m_p, 0, RK3288_CLKSEL_CON(32), 14, 2, MFLAGS, 8, 5, DFLAGS, RK3288_CLKGATE_CON(3), 11, GFLAGS), + /* + * We use aclk_vdpu by default GRF_SOC_CON0[7] setting in system, + * so we ignore the mux and make clocks nodes as following, + */ + GATE(ACLK_VCODEC, "aclk_vcodec", "aclk_vdpu", 0, + RK3288_CLKGATE_CON(9), 0, GFLAGS), + /* + * We introduce a virtul node of hclk_vodec_pre_v to split one clock + * struct with a gate and a fix divider into two node in software. + */ + GATE(0, "hclk_vcodec_pre_v", "aclk_vdpu", 0, + RK3288_CLKGATE_CON(3), 10, GFLAGS), + GATE(HCLK_VCODEC, "hclk_vcodec", "hclk_vcodec_pre", 0, + RK3288_CLKGATE_CON(9), 1, GFLAGS), COMPOSITE(0, "aclk_vio0", mux_pll_src_cpll_gpll_usb480m_p, 0, RK3288_CLKSEL_CON(31), 6, 2, MFLAGS, 0, 5, DFLAGS, @@ -309,7 +385,7 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { COMPOSITE(0, "aclk_rga_pre", mux_pll_src_cpll_gpll_usb480m_p, 0, RK3288_CLKSEL_CON(30), 6, 2, MFLAGS, 0, 5, DFLAGS, RK3288_CLKGATE_CON(3), 5, GFLAGS), - COMPOSITE(0, "sclk_rga", mux_pll_src_cpll_gpll_usb480m_p, 0, + COMPOSITE(SCLK_RGA, "sclk_rga", mux_pll_src_cpll_gpll_usb480m_p, 0, RK3288_CLKSEL_CON(30), 14, 2, MFLAGS, 8, 5, DFLAGS, RK3288_CLKGATE_CON(3), 4, GFLAGS), @@ -320,35 +396,35 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { RK3288_CLKSEL_CON(29), 6, 2, MFLAGS, 8, 8, DFLAGS, RK3288_CLKGATE_CON(3), 3, GFLAGS), - COMPOSITE_NODIV(0, "sclk_edp_24m", mux_edp_24m_p, 0, + COMPOSITE_NODIV(SCLK_EDP_24M, "sclk_edp_24m", mux_edp_24m_p, 0, RK3288_CLKSEL_CON(28), 15, 1, MFLAGS, RK3288_CLKGATE_CON(3), 12, GFLAGS), - COMPOSITE(0, "sclk_edp", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(SCLK_EDP, "sclk_edp", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(28), 6, 2, MFLAGS, 0, 6, DFLAGS, RK3288_CLKGATE_CON(3), 13, GFLAGS), - COMPOSITE(0, "sclk_isp", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(SCLK_ISP, "sclk_isp", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(6), 6, 2, MFLAGS, 0, 6, DFLAGS, RK3288_CLKGATE_CON(3), 14, GFLAGS), - COMPOSITE(0, "sclk_isp_jpe", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(SCLK_ISP_JPE, "sclk_isp_jpe", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(6), 14, 2, MFLAGS, 8, 6, DFLAGS, RK3288_CLKGATE_CON(3), 15, GFLAGS), - GATE(0, "sclk_hdmi_hdcp", "xin24m", 0, + GATE(SCLK_HDMI_HDCP, "sclk_hdmi_hdcp", "xin24m", 0, RK3288_CLKGATE_CON(5), 12, GFLAGS), - GATE(0, "sclk_hdmi_cec", "xin32k", 0, + GATE(SCLK_HDMI_CEC, "sclk_hdmi_cec", "xin32k", 0, RK3288_CLKGATE_CON(5), 11, GFLAGS), - COMPOSITE(0, "aclk_hevc", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(ACLK_HEVC, "aclk_hevc", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(39), 14, 2, MFLAGS, 8, 5, DFLAGS, RK3288_CLKGATE_CON(13), 13, GFLAGS), - DIV(0, "hclk_hevc", "aclk_hevc", 0, + DIV(HCLK_HEVC, "hclk_hevc", "aclk_hevc", 0, RK3288_CLKSEL_CON(40), 12, 2, DFLAGS), - COMPOSITE(0, "sclk_hevc_cabac", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(SCLK_HEVC_CABAC, "sclk_hevc_cabac", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(42), 6, 2, MFLAGS, 0, 5, DFLAGS, RK3288_CLKGATE_CON(13), 14, GFLAGS), - COMPOSITE(0, "sclk_hevc_core", mux_pll_src_cpll_gpll_npll_p, 0, + COMPOSITE(SCLK_HEVC_CORE, "sclk_hevc_core", mux_pll_src_cpll_gpll_npll_p, 0, RK3288_CLKSEL_CON(42), 14, 2, MFLAGS, 8, 5, DFLAGS, RK3288_CLKGATE_CON(13), 15, GFLAGS), @@ -371,13 +447,13 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { COMPOSITE(0, "aclk_peri_src", mux_pll_src_cpll_gpll_p, 0, RK3288_CLKSEL_CON(10), 15, 1, MFLAGS, 0, 5, DFLAGS, RK3288_CLKGATE_CON(2), 0, GFLAGS), - COMPOSITE_NOMUX(0, "pclk_peri", "aclk_peri_src", 0, + COMPOSITE_NOMUX(PCLK_PERI, "pclk_peri", "aclk_peri_src", 0, RK3288_CLKSEL_CON(10), 12, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO, RK3288_CLKGATE_CON(2), 3, GFLAGS), - COMPOSITE_NOMUX(0, "hclk_peri", "aclk_peri_src", 0, + COMPOSITE_NOMUX(HCLK_PERI, "hclk_peri", "aclk_peri_src", 0, RK3288_CLKSEL_CON(10), 8, 2, DFLAGS | CLK_DIVIDER_POWER_OF_TWO, RK3288_CLKGATE_CON(2), 2, GFLAGS), - GATE(0, "aclk_peri", "aclk_peri_src", 0, + GATE(ACLK_PERI, "aclk_peri", "aclk_peri_src", 0, RK3288_CLKGATE_CON(2), 1, GFLAGS), /* @@ -643,34 +719,34 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { GATE(HCLK_RGA, "hclk_rga", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 1, GFLAGS), GATE(HCLK_VOP0, "hclk_vop0", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 6, GFLAGS), GATE(HCLK_VOP1, "hclk_vop1", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 8, GFLAGS), - GATE(0, "hclk_vio_ahb_arbi", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 9, GFLAGS), - GATE(0, "hclk_vio_niu", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 10, GFLAGS), - GATE(0, "hclk_vip", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 15, GFLAGS), + GATE(HCLK_VIO_AHB_ARBI, "hclk_vio_ahb_arbi", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 9, GFLAGS), + GATE(HCLK_VIO_NIU, "hclk_vio_niu", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 10, GFLAGS), + GATE(HCLK_VIP, "hclk_vip", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 15, GFLAGS), GATE(HCLK_IEP, "hclk_iep", "hclk_vio", 0, RK3288_CLKGATE_CON(15), 3, GFLAGS), GATE(HCLK_ISP, "hclk_isp", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 1, GFLAGS), - GATE(0, "hclk_vio2_h2p", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 10, GFLAGS), - GATE(0, "pclk_mipi_dsi0", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 4, GFLAGS), - GATE(0, "pclk_mipi_dsi1", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 5, GFLAGS), - GATE(0, "pclk_mipi_csi", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 6, GFLAGS), - GATE(0, "pclk_lvds_phy", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 7, GFLAGS), - GATE(0, "pclk_edp_ctrl", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 8, GFLAGS), - GATE(0, "pclk_hdmi_ctrl", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 9, GFLAGS), - GATE(0, "pclk_vio2_h2p", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 11, GFLAGS), + GATE(HCLK_VIO2_H2P, "hclk_vio2_h2p", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 10, GFLAGS), + GATE(PCLK_MIPI_DSI0, "pclk_mipi_dsi0", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 4, GFLAGS), + GATE(PCLK_MIPI_DSI1, "pclk_mipi_dsi1", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 5, GFLAGS), + GATE(PCLK_MIPI_CSI, "pclk_mipi_csi", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 6, GFLAGS), + GATE(PCLK_LVDS_PHY, "pclk_lvds_phy", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 7, GFLAGS), + GATE(PCLK_EDP_CTRL, "pclk_edp_ctrl", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 8, GFLAGS), + GATE(PCLK_HDMI_CTRL, "pclk_hdmi_ctrl", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 9, GFLAGS), + GATE(PCLK_VIO2_H2P, "pclk_vio2_h2p", "hclk_vio", 0, RK3288_CLKGATE_CON(16), 11, GFLAGS), /* aclk_vio0 gates */ GATE(ACLK_VOP0, "aclk_vop0", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 5, GFLAGS), - GATE(0, "aclk_iep", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 2, GFLAGS), - GATE(0, "aclk_vio0_niu", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 11, GFLAGS), - GATE(0, "aclk_vip", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 14, GFLAGS), + GATE(ACLK_IEP, "aclk_iep", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 2, GFLAGS), + GATE(ACLK_VIO0_NIU, "aclk_vio0_niu", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 11, GFLAGS), + GATE(ACLK_VIP, "aclk_vip", "aclk_vio0", 0, RK3288_CLKGATE_CON(15), 14, GFLAGS), /* aclk_vio1 gates */ GATE(ACLK_VOP1, "aclk_vop1", "aclk_vio1", 0, RK3288_CLKGATE_CON(15), 7, GFLAGS), - GATE(0, "aclk_isp", "aclk_vio1", 0, RK3288_CLKGATE_CON(16), 2, GFLAGS), - GATE(0, "aclk_vio1_niu", "aclk_vio1", 0, RK3288_CLKGATE_CON(15), 12, GFLAGS), + GATE(ACLK_ISP, "aclk_isp", "aclk_vio1", 0, RK3288_CLKGATE_CON(16), 2, GFLAGS), + GATE(ACLK_VIO1_NIU, "aclk_vio1_niu", "aclk_vio1", 0, RK3288_CLKGATE_CON(15), 12, GFLAGS), /* aclk_rga_pre gates */ GATE(ACLK_RGA, "aclk_rga", "aclk_rga_pre", 0, RK3288_CLKGATE_CON(15), 0, GFLAGS), - GATE(0, "aclk_rga_niu", "aclk_rga_pre", 0, RK3288_CLKGATE_CON(15), 13, GFLAGS), + GATE(ACLK_RGA_NIU, "aclk_rga_niu", "aclk_rga_pre", 0, RK3288_CLKGATE_CON(15), 13, GFLAGS), /* * Other ungrouped clocks. @@ -680,6 +756,12 @@ static struct rockchip_clk_branch rk3288_clk_branches[] __initdata = { GATE(0, "pclk_isp_in", "ext_isp", 0, RK3288_CLKGATE_CON(16), 3, GFLAGS), }; +static const char *rk3288_critical_clocks[] __initconst = { + "aclk_cpu", + "aclk_peri", + "hclk_peri", +}; + static void __init rk3288_clk_init(struct device_node *np) { void __iomem *reg_base; @@ -705,13 +787,28 @@ static void __init rk3288_clk_init(struct device_node *np) pr_warn("%s: could not register clock usb480m: %ld\n", __func__, PTR_ERR(clk)); + clk = clk_register_fixed_factor(NULL, "hclk_vcodec_pre", + "hclk_vcodec_pre_v", 0, 1, 4); + if (IS_ERR(clk)) + pr_warn("%s: could not register clock hclk_vcodec_pre: %ld\n", + __func__, PTR_ERR(clk)); + rockchip_clk_register_plls(rk3288_pll_clks, ARRAY_SIZE(rk3288_pll_clks), - RK3288_GRF_SOC_STATUS); + RK3288_GRF_SOC_STATUS1); rockchip_clk_register_branches(rk3288_clk_branches, ARRAY_SIZE(rk3288_clk_branches)); + rockchip_clk_protect_critical(rk3288_critical_clocks, + ARRAY_SIZE(rk3288_critical_clocks)); - rockchip_register_softrst(np, 9, reg_base + RK3288_SOFTRST_CON(0), + rockchip_clk_register_armclk(ARMCLK, "armclk", + mux_armclk_p, ARRAY_SIZE(mux_armclk_p), + &rk3288_cpuclk_data, rk3288_cpuclk_rates, + ARRAY_SIZE(rk3288_cpuclk_rates)); + + rockchip_register_softrst(np, 12, reg_base + RK3288_SOFTRST_CON(0), ROCKCHIP_SOFTRST_HIWORD_MASK); + + rockchip_register_restart_notifier(RK3288_GLB_SRST_FST); } CLK_OF_DECLARE(rk3288_cru, "rockchip,rk3288-cru", rk3288_clk_init); diff --git a/drivers/clk/rockchip/clk.c b/drivers/clk/rockchip/clk.c index 278cf9dd1e2..1e68bff481b 100644 --- a/drivers/clk/rockchip/clk.c +++ b/drivers/clk/rockchip/clk.c @@ -25,6 +25,7 @@ #include <linux/clk-provider.h> #include <linux/mfd/syscon.h> #include <linux/regmap.h> +#include <linux/reboot.h> #include "clk.h" /** @@ -37,7 +38,7 @@ * * sometimes without one of those components. */ -struct clk *rockchip_clk_register_branch(const char *name, +static struct clk *rockchip_clk_register_branch(const char *name, const char **parent_names, u8 num_parents, void __iomem *base, int muxdiv_offset, u8 mux_shift, u8 mux_width, u8 mux_flags, u8 div_shift, u8 div_width, u8 div_flags, @@ -103,6 +104,54 @@ struct clk *rockchip_clk_register_branch(const char *name, return clk; } +static struct clk *rockchip_clk_register_frac_branch(const char *name, + const char **parent_names, u8 num_parents, void __iomem *base, + int muxdiv_offset, u8 div_flags, + int gate_offset, u8 gate_shift, u8 gate_flags, + unsigned long flags, spinlock_t *lock) +{ + struct clk *clk; + struct clk_gate *gate = NULL; + struct clk_fractional_divider *div = NULL; + const struct clk_ops *div_ops = NULL, *gate_ops = NULL; + + if (gate_offset >= 0) { + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->flags = gate_flags; + gate->reg = base + gate_offset; + gate->bit_idx = gate_shift; + gate->lock = lock; + gate_ops = &clk_gate_ops; + } + + if (muxdiv_offset < 0) + return ERR_PTR(-EINVAL); + + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOMEM); + + div->flags = div_flags; + div->reg = base + muxdiv_offset; + div->mshift = 16; + div->mmask = 0xffff0000; + div->nshift = 0; + div->nmask = 0xffff; + div->lock = lock; + div_ops = &clk_fractional_divider_ops; + + clk = clk_register_composite(NULL, name, parent_names, num_parents, + NULL, NULL, + &div->hw, div_ops, + gate ? &gate->hw : NULL, gate_ops, + flags); + + return clk; +} + static DEFINE_SPINLOCK(clk_lock); static struct clk **clk_table; static void __iomem *reg_base; @@ -197,8 +246,14 @@ void __init rockchip_clk_register_branches( list->div_flags, &clk_lock); break; case branch_fraction_divider: - /* unimplemented */ - continue; + /* keep all gates untouched for now */ + flags |= CLK_IGNORE_UNUSED; + + clk = rockchip_clk_register_frac_branch(list->name, + list->parent_names, list->num_parents, + reg_base, list->muxdiv_offset, list->div_flags, + list->gate_offset, list->gate_shift, + list->gate_flags, flags, &clk_lock); break; case branch_gate: flags |= CLK_SET_RATE_PARENT; @@ -242,3 +297,61 @@ void __init rockchip_clk_register_branches( rockchip_clk_add_lookup(clk, list->id); } } + +void __init rockchip_clk_register_armclk(unsigned int lookup_id, + const char *name, const char **parent_names, + u8 num_parents, + const struct rockchip_cpuclk_reg_data *reg_data, + const struct rockchip_cpuclk_rate_table *rates, + int nrates) +{ + struct clk *clk; + + clk = rockchip_clk_register_cpuclk(name, parent_names, num_parents, + reg_data, rates, nrates, reg_base, + &clk_lock); + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s: %ld\n", + __func__, name, PTR_ERR(clk)); + return; + } + + rockchip_clk_add_lookup(clk, lookup_id); +} + +void __init rockchip_clk_protect_critical(const char *clocks[], int nclocks) +{ + int i; + + /* Protect the clocks that needs to stay on */ + for (i = 0; i < nclocks; i++) { + struct clk *clk = __clk_lookup(clocks[i]); + + if (clk) + clk_prepare_enable(clk); + } +} + +static unsigned int reg_restart; +static int rockchip_restart_notify(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + writel(0xfdb9, reg_base + reg_restart); + return NOTIFY_DONE; +} + +static struct notifier_block rockchip_restart_handler = { + .notifier_call = rockchip_restart_notify, + .priority = 128, +}; + +void __init rockchip_register_restart_notifier(unsigned int reg) +{ + int ret; + + reg_restart = reg; + ret = register_restart_handler(&rockchip_restart_handler); + if (ret) + pr_err("%s: cannot register restart handler, %d\n", + __func__, ret); +} diff --git a/drivers/clk/rockchip/clk.h b/drivers/clk/rockchip/clk.h index 887cbdeca2a..ca009ab0a33 100644 --- a/drivers/clk/rockchip/clk.h +++ b/drivers/clk/rockchip/clk.h @@ -120,6 +120,38 @@ struct clk *rockchip_clk_register_pll(enum rockchip_pll_type pll_type, struct rockchip_pll_rate_table *rate_table, spinlock_t *lock); +struct rockchip_cpuclk_clksel { + int reg; + u32 val; +}; + +#define ROCKCHIP_CPUCLK_NUM_DIVIDERS 2 +struct rockchip_cpuclk_rate_table { + unsigned long prate; + struct rockchip_cpuclk_clksel divs[ROCKCHIP_CPUCLK_NUM_DIVIDERS]; +}; + +/** + * struct rockchip_cpuclk_reg_data: describes register offsets and masks of the cpuclock + * @core_reg: register offset of the core settings register + * @div_core_shift: core divider offset used to divide the pll value + * @div_core_mask: core divider mask + * @mux_core_shift: offset of the core multiplexer + */ +struct rockchip_cpuclk_reg_data { + int core_reg; + u8 div_core_shift; + u32 div_core_mask; + int mux_core_reg; + u8 mux_core_shift; +}; + +struct clk *rockchip_clk_register_cpuclk(const char *name, + const char **parent_names, u8 num_parents, + const struct rockchip_cpuclk_reg_data *reg_data, + const struct rockchip_cpuclk_rate_table *rates, + int nrates, void __iomem *reg_base, spinlock_t *lock); + #define PNAME(x) static const char *x[] __initconst enum rockchip_clk_branch_type { @@ -329,6 +361,13 @@ void rockchip_clk_register_branches(struct rockchip_clk_branch *clk_list, unsigned int nr_clk); void rockchip_clk_register_plls(struct rockchip_pll_clock *pll_list, unsigned int nr_pll, int grf_lock_offset); +void rockchip_clk_register_armclk(unsigned int lookup_id, const char *name, + const char **parent_names, u8 num_parents, + const struct rockchip_cpuclk_reg_data *reg_data, + const struct rockchip_cpuclk_rate_table *rates, + int nrates); +void rockchip_clk_protect_critical(const char *clocks[], int nclocks); +void rockchip_register_restart_notifier(unsigned int reg); #define ROCKCHIP_SOFTRST_HIWORD_MASK BIT(0) diff --git a/drivers/clk/samsung/clk-exynos3250.c b/drivers/clk/samsung/clk-exynos3250.c index dc85f8e7a2d..6e6cca39208 100644 --- a/drivers/clk/samsung/clk-exynos3250.c +++ b/drivers/clk/samsung/clk-exynos3250.c @@ -110,7 +110,14 @@ enum exynos3250_plls { nr_plls }; +/* list of PLLs in DMC block to be registered */ +enum exynos3250_dmc_plls { + bpll, epll, + nr_dmc_plls +}; + static void __iomem *reg_base; +static void __iomem *dmc_reg_base; /* * Support for CMU save/restore across system suspends @@ -266,6 +273,7 @@ PNAME(group_sclk_cam_blk_p) = { "xxti", "xusbxti", "none", "none", "none", "none", "div_mpll_pre", "mout_epll_user", "mout_vpll", + "none", "none", "none", "div_cam_blk_320", }; PNAME(group_sclk_fimd0_p) = { "xxti", "xusbxti", "m_bitclkhsdiv4_2l", "none", @@ -353,8 +361,8 @@ static struct samsung_mux_clock mux_clks[] __initdata = { /* SRC_FSYS */ MUX(CLK_MOUT_TSADC, "mout_tsadc", group_sclk_p, SRC_FSYS, 28, 4), - MUX(CLK_MOUT_MMC1, "mout_mmc1", group_sclk_p, SRC_FSYS, 4, 3), - MUX(CLK_MOUT_MMC0, "mout_mmc0", group_sclk_p, SRC_FSYS, 0, 3), + MUX(CLK_MOUT_MMC1, "mout_mmc1", group_sclk_p, SRC_FSYS, 4, 4), + MUX(CLK_MOUT_MMC0, "mout_mmc0", group_sclk_p, SRC_FSYS, 0, 4), /* SRC_PERIL0 */ MUX(CLK_MOUT_UART1, "mout_uart1", group_sclk_p, SRC_PERIL0, 4, 4), @@ -423,7 +431,7 @@ static struct samsung_div_clock div_clks[] __initdata = { DIV(CLK_DIV_SPI1_ISP, "div_spi1_isp", "mout_spi1_isp", DIV_ISP, 16, 4), DIV_F(CLK_DIV_SPI0_ISP_PRE, "div_spi0_isp_pre", "div_spi0_isp", DIV_ISP, 8, 8, CLK_SET_RATE_PARENT, 0), - DIV(CLK_DIV_SPI0_ISP, "div_spi0_isp", "mout_spi0_isp", DIV_ISP, 0, 4), + DIV(CLK_DIV_SPI0_ISP, "div_spi0_isp", "mout_spi0_isp", DIV_ISP, 4, 4), /* DIV_FSYS0 */ DIV_F(CLK_DIV_TSADC_PRE, "div_tsadc_pre", "div_tsadc", DIV_FSYS0, 8, 8, @@ -724,6 +732,25 @@ static struct samsung_pll_rate_table exynos3250_pll_rates[] = { { /* sentinel */ } }; +/* EPLL */ +static struct samsung_pll_rate_table exynos3250_epll_rates[] = { + PLL_36XX_RATE(800000000, 200, 3, 1, 0), + PLL_36XX_RATE(288000000, 96, 2, 2, 0), + PLL_36XX_RATE(192000000, 128, 2, 3, 0), + PLL_36XX_RATE(144000000, 96, 2, 3, 0), + PLL_36XX_RATE( 96000000, 128, 2, 4, 0), + PLL_36XX_RATE( 84000000, 112, 2, 4, 0), + PLL_36XX_RATE( 80000004, 106, 2, 4, 43691), + PLL_36XX_RATE( 73728000, 98, 2, 4, 19923), + PLL_36XX_RATE( 67737598, 270, 3, 5, 62285), + PLL_36XX_RATE( 65535999, 174, 2, 5, 49982), + PLL_36XX_RATE( 50000000, 200, 3, 5, 0), + PLL_36XX_RATE( 49152002, 131, 2, 5, 4719), + PLL_36XX_RATE( 48000000, 128, 2, 5, 0), + PLL_36XX_RATE( 45158401, 180, 3, 5, 41524), + { /* sentinel */ } +}; + /* VPLL */ static struct samsung_pll_rate_table exynos3250_vpll_rates[] = { PLL_36XX_RATE(600000000, 100, 2, 1, 0), @@ -821,3 +848,172 @@ static void __init exynos3250_cmu_init(struct device_node *np) samsung_clk_of_add_provider(np, ctx); } CLK_OF_DECLARE(exynos3250_cmu, "samsung,exynos3250-cmu", exynos3250_cmu_init); + +/* + * CMU DMC + */ + +#define BPLL_LOCK 0x0118 +#define BPLL_CON0 0x0218 +#define BPLL_CON1 0x021c +#define BPLL_CON2 0x0220 +#define SRC_DMC 0x0300 +#define DIV_DMC1 0x0504 +#define GATE_BUS_DMC0 0x0700 +#define GATE_BUS_DMC1 0x0704 +#define GATE_BUS_DMC2 0x0708 +#define GATE_BUS_DMC3 0x070c +#define GATE_SCLK_DMC 0x0800 +#define GATE_IP_DMC0 0x0900 +#define GATE_IP_DMC1 0x0904 +#define EPLL_LOCK 0x1110 +#define EPLL_CON0 0x1114 +#define EPLL_CON1 0x1118 +#define EPLL_CON2 0x111c +#define SRC_EPLL 0x1120 + +/* + * Support for CMU save/restore across system suspends + */ +#ifdef CONFIG_PM_SLEEP +static struct samsung_clk_reg_dump *exynos3250_dmc_clk_regs; + +static unsigned long exynos3250_cmu_dmc_clk_regs[] __initdata = { + BPLL_LOCK, + BPLL_CON0, + BPLL_CON1, + BPLL_CON2, + SRC_DMC, + DIV_DMC1, + GATE_BUS_DMC0, + GATE_BUS_DMC1, + GATE_BUS_DMC2, + GATE_BUS_DMC3, + GATE_SCLK_DMC, + GATE_IP_DMC0, + GATE_IP_DMC1, + EPLL_LOCK, + EPLL_CON0, + EPLL_CON1, + EPLL_CON2, + SRC_EPLL, +}; + +static int exynos3250_dmc_clk_suspend(void) +{ + samsung_clk_save(dmc_reg_base, exynos3250_dmc_clk_regs, + ARRAY_SIZE(exynos3250_cmu_dmc_clk_regs)); + return 0; +} + +static void exynos3250_dmc_clk_resume(void) +{ + samsung_clk_restore(dmc_reg_base, exynos3250_dmc_clk_regs, + ARRAY_SIZE(exynos3250_cmu_dmc_clk_regs)); +} + +static struct syscore_ops exynos3250_dmc_clk_syscore_ops = { + .suspend = exynos3250_dmc_clk_suspend, + .resume = exynos3250_dmc_clk_resume, +}; + +static void exynos3250_dmc_clk_sleep_init(void) +{ + exynos3250_dmc_clk_regs = + samsung_clk_alloc_reg_dump(exynos3250_cmu_dmc_clk_regs, + ARRAY_SIZE(exynos3250_cmu_dmc_clk_regs)); + if (!exynos3250_dmc_clk_regs) { + pr_warn("%s: Failed to allocate sleep save data\n", __func__); + goto err; + } + + register_syscore_ops(&exynos3250_dmc_clk_syscore_ops); + return; +err: + kfree(exynos3250_dmc_clk_regs); +} +#else +static inline void exynos3250_dmc_clk_sleep_init(void) { } +#endif + +PNAME(mout_epll_p) = { "fin_pll", "fout_epll", }; +PNAME(mout_bpll_p) = { "fin_pll", "fout_bpll", }; +PNAME(mout_mpll_mif_p) = { "fin_pll", "sclk_mpll_mif", }; +PNAME(mout_dphy_p) = { "mout_mpll_mif", "mout_bpll", }; + +static struct samsung_mux_clock dmc_mux_clks[] __initdata = { + /* + * NOTE: Following table is sorted by register address in ascending + * order and then bitfield shift in descending order, as it is done + * in the User's Manual. When adding new entries, please make sure + * that the order is preserved, to avoid merge conflicts and make + * further work with defined data easier. + */ + + /* SRC_DMC */ + MUX(CLK_MOUT_MPLL_MIF, "mout_mpll_mif", mout_mpll_mif_p, SRC_DMC, 12, 1), + MUX(CLK_MOUT_BPLL, "mout_bpll", mout_bpll_p, SRC_DMC, 10, 1), + MUX(CLK_MOUT_DPHY, "mout_dphy", mout_dphy_p, SRC_DMC, 8, 1), + MUX(CLK_MOUT_DMC_BUS, "mout_dmc_bus", mout_dphy_p, SRC_DMC, 4, 1), + + /* SRC_EPLL */ + MUX(CLK_MOUT_EPLL, "mout_epll", mout_epll_p, SRC_EPLL, 4, 1), +}; + +static struct samsung_div_clock dmc_div_clks[] __initdata = { + /* + * NOTE: Following table is sorted by register address in ascending + * order and then bitfield shift in descending order, as it is done + * in the User's Manual. When adding new entries, please make sure + * that the order is preserved, to avoid merge conflicts and make + * further work with defined data easier. + */ + + /* DIV_DMC1 */ + DIV(CLK_DIV_DMC, "div_dmc", "div_dmc_pre", DIV_DMC1, 27, 3), + DIV(CLK_DIV_DPHY, "div_dphy", "mout_dphy", DIV_DMC1, 23, 3), + DIV(CLK_DIV_DMC_PRE, "div_dmc_pre", "mout_dmc_bus", DIV_DMC1, 19, 2), + DIV(CLK_DIV_DMCP, "div_dmcp", "div_dmcd", DIV_DMC1, 15, 3), + DIV(CLK_DIV_DMCD, "div_dmcd", "div_dmc", DIV_DMC1, 11, 3), +}; + +static struct samsung_pll_clock exynos3250_dmc_plls[nr_dmc_plls] __initdata = { + [bpll] = PLL(pll_35xx, CLK_FOUT_BPLL, "fout_bpll", "fin_pll", + BPLL_LOCK, BPLL_CON0, NULL), + [epll] = PLL(pll_36xx, CLK_FOUT_EPLL, "fout_epll", "fin_pll", + EPLL_LOCK, EPLL_CON0, NULL), +}; + +static void __init exynos3250_cmu_dmc_init(struct device_node *np) +{ + struct samsung_clk_provider *ctx; + + dmc_reg_base = of_iomap(np, 0); + if (!dmc_reg_base) + panic("%s: failed to map registers\n", __func__); + + ctx = samsung_clk_init(np, dmc_reg_base, NR_CLKS_DMC); + if (!ctx) + panic("%s: unable to allocate context.\n", __func__); + + exynos3250_dmc_plls[bpll].rate_table = exynos3250_pll_rates; + exynos3250_dmc_plls[epll].rate_table = exynos3250_epll_rates; + + pr_err("CLK registering epll bpll: %d, %d, %d, %d\n", + exynos3250_dmc_plls[bpll].rate_table[0].rate, + exynos3250_dmc_plls[bpll].rate_table[0].mdiv, + exynos3250_dmc_plls[bpll].rate_table[0].pdiv, + exynos3250_dmc_plls[bpll].rate_table[0].sdiv + ); + samsung_clk_register_pll(ctx, exynos3250_dmc_plls, + ARRAY_SIZE(exynos3250_dmc_plls), dmc_reg_base); + + samsung_clk_register_mux(ctx, dmc_mux_clks, ARRAY_SIZE(dmc_mux_clks)); + samsung_clk_register_div(ctx, dmc_div_clks, ARRAY_SIZE(dmc_div_clks)); + + exynos3250_dmc_clk_sleep_init(); + + samsung_clk_of_add_provider(np, ctx); +} +CLK_OF_DECLARE(exynos3250_cmu_dmc, "samsung,exynos3250-cmu-dmc", + exynos3250_cmu_dmc_init); diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c index ac163d7f5bc..940f02837b8 100644 --- a/drivers/clk/samsung/clk-exynos4.c +++ b/drivers/clk/samsung/clk-exynos4.c @@ -517,7 +517,7 @@ static struct samsung_fixed_factor_clock exynos4_fixed_factor_clks[] __initdata FFACTOR(0, "sclk_apll_div_2", "sclk_apll", 1, 2, 0), FFACTOR(0, "fout_mpll_div_2", "fout_mpll", 1, 2, 0), FFACTOR(0, "fout_apll_div_2", "fout_apll", 1, 2, 0), - FFACTOR(0, "arm_clk_div_2", "arm_clk", 1, 2, 0), + FFACTOR(0, "arm_clk_div_2", "div_core2", 1, 2, 0), }; static struct samsung_fixed_factor_clock exynos4210_fixed_factor_clks[] __initdata = { @@ -535,7 +535,7 @@ static struct samsung_fixed_factor_clock exynos4x12_fixed_factor_clks[] __initda static struct samsung_mux_clock exynos4_mux_clks[] __initdata = { MUX_FA(CLK_MOUT_APLL, "mout_apll", mout_apll_p, SRC_CPU, 0, 1, CLK_SET_RATE_PARENT, 0, "mout_apll"), - MUX(0, "mout_hdmi", mout_hdmi_p, SRC_TV, 0, 1), + MUX(CLK_MOUT_HDMI, "mout_hdmi", mout_hdmi_p, SRC_TV, 0, 1), MUX(0, "mout_mfc1", sclk_evpll_p, SRC_MFC, 4, 1), MUX(0, "mout_mfc", mout_mfc_p, SRC_MFC, 8, 1), MUX_F(CLK_MOUT_G3D1, "mout_g3d1", sclk_evpll_p, SRC_G3D, 4, 1, @@ -569,7 +569,7 @@ static struct samsung_mux_clock exynos4210_mux_clks[] __initdata = { MUX(0, "mout_aclk100", sclk_ampll_p4210, SRC_TOP0, 16, 1), MUX(0, "mout_aclk160", sclk_ampll_p4210, SRC_TOP0, 20, 1), MUX(0, "mout_aclk133", sclk_ampll_p4210, SRC_TOP0, 24, 1), - MUX(0, "mout_mixer", mout_mixer_p4210, SRC_TV, 4, 1), + MUX(CLK_MOUT_MIXER, "mout_mixer", mout_mixer_p4210, SRC_TV, 4, 1), MUX(0, "mout_dac", mout_dac_p4210, SRC_TV, 8, 1), MUX(0, "mout_g2d0", sclk_ampll_p4210, E4210_SRC_IMAGE, 0, 1), MUX(0, "mout_g2d1", sclk_evpll_p, E4210_SRC_IMAGE, 4, 1), @@ -719,7 +719,7 @@ static struct samsung_div_clock exynos4_div_clks[] __initdata = { DIV(0, "div_periph", "div_core2", DIV_CPU0, 12, 3), DIV(0, "div_atb", "mout_core", DIV_CPU0, 16, 3), DIV(0, "div_pclk_dbg", "div_atb", DIV_CPU0, 20, 3), - DIV(0, "div_core2", "div_core", DIV_CPU0, 28, 3), + DIV(CLK_ARM_CLK, "div_core2", "div_core", DIV_CPU0, 28, 3), DIV(0, "div_copy", "mout_hpm", DIV_CPU1, 0, 3), DIV(0, "div_hpm", "div_copy", DIV_CPU1, 4, 3), DIV(0, "div_clkout_cpu", "mout_clkout_cpu", CLKOUT_CMU_CPU, 8, 6), @@ -733,8 +733,7 @@ static struct samsung_div_clock exynos4_div_clks[] __initdata = { DIV(0, "div_csis0", "mout_csis0", DIV_CAM, 24, 4), DIV(0, "div_csis1", "mout_csis1", DIV_CAM, 28, 4), DIV(CLK_SCLK_MFC, "sclk_mfc", "mout_mfc", DIV_MFC, 0, 4), - DIV_F(0, "div_g3d", "mout_g3d", DIV_G3D, 0, 4, - CLK_SET_RATE_PARENT, 0), + DIV(CLK_SCLK_G3D, "sclk_g3d", "mout_g3d", DIV_G3D, 0, 4), DIV(0, "div_fimd0", "mout_fimd0", DIV_LCD0, 0, 4), DIV(0, "div_mipi0", "mout_mipi0", DIV_LCD0, 16, 4), DIV(0, "div_audio0", "mout_audio0", DIV_MAUDIO, 0, 4), @@ -769,7 +768,6 @@ static struct samsung_div_clock exynos4_div_clks[] __initdata = { DIV(0, "div_spi_pre2", "div_spi2", DIV_PERIL2, 8, 8), DIV(0, "div_audio1", "mout_audio1", DIV_PERIL4, 0, 4), DIV(0, "div_audio2", "mout_audio2", DIV_PERIL4, 16, 4), - DIV(CLK_ARM_CLK, "arm_clk", "div_core2", DIV_CPU0, 28, 3), DIV(CLK_SCLK_APLL, "sclk_apll", "mout_apll", DIV_CPU0, 24, 3), DIV_F(0, "div_mipi_pre0", "div_mipi0", DIV_LCD0, 20, 4, CLK_SET_RATE_PARENT, 0), @@ -857,8 +855,7 @@ static struct samsung_gate_clock exynos4_gate_clks[] __initdata = { 0), GATE(CLK_TSI, "tsi", "aclk133", GATE_IP_FSYS, 4, 0, 0), GATE(CLK_SROMC, "sromc", "aclk133", GATE_IP_FSYS, 11, 0, 0), - GATE(CLK_SCLK_G3D, "sclk_g3d", "div_g3d", GATE_IP_G3D, 0, - CLK_SET_RATE_PARENT, 0), + GATE(CLK_G3D, "g3d", "aclk200", GATE_IP_G3D, 0, 0, 0), GATE(CLK_PPMUG3D, "ppmug3d", "aclk200", GATE_IP_G3D, 1, 0, 0), GATE(CLK_USB_DEVICE, "usb_device", "aclk133", GATE_IP_FSYS, 13, 0, 0), GATE(CLK_ONENAND, "onenand", "aclk133", GATE_IP_FSYS, 15, 0, 0), @@ -1183,6 +1180,7 @@ static struct samsung_gate_clock exynos4x12_gate_clks[] __initdata = { GATE(CLK_SPI1_ISP, "spi1_isp", "aclk200", E4X12_GATE_ISP1, 13, CLK_IGNORE_UNUSED | CLK_GET_RATE_NOCACHE, 0), GATE(CLK_G2D, "g2d", "aclk200", GATE_IP_DMC, 23, 0, 0), + GATE(CLK_SMMU_G2D, "smmu_g2d", "aclk200", GATE_IP_DMC, 24, 0, 0), GATE(CLK_TMU_APBIF, "tmu_apbif", "aclk100", E4X12_GATE_IP_PERIR, 17, 0, 0), }; @@ -1486,7 +1484,7 @@ static void __init exynos4_clk_init(struct device_node *np, exynos4_soc == EXYNOS4210 ? "Exynos4210" : "Exynos4x12", _get_rate("sclk_apll"), _get_rate("sclk_mpll"), _get_rate("sclk_epll"), _get_rate("sclk_vpll"), - _get_rate("arm_clk")); + _get_rate("div_core2")); } diff --git a/drivers/clk/samsung/clk-exynos5260.c b/drivers/clk/samsung/clk-exynos5260.c index ce3de97e5f1..2527e39aadc 100644 --- a/drivers/clk/samsung/clk-exynos5260.c +++ b/drivers/clk/samsung/clk-exynos5260.c @@ -1581,7 +1581,7 @@ struct samsung_fixed_rate_clock fixed_rate_clks[] __initdata = { FRATE(PHYCLK_HDMI_LINK_O_TMDS_CLKHI, "phyclk_hdmi_link_o_tmds_clkhi", NULL, CLK_IS_ROOT, 125000000), FRATE(PHYCLK_MIPI_DPHY_4L_M_TXBYTECLKHS, - "phyclk_mipi_dphy_4l_m_txbyteclkhs" , NULL, + "phyclk_mipi_dphy_4l_m_txbyte_clkhs" , NULL, CLK_IS_ROOT, 187500000), FRATE(PHYCLK_DPTX_PHY_O_REF_CLK_24M, "phyclk_dptx_phy_o_ref_clk_24m", NULL, CLK_IS_ROOT, 24000000), diff --git a/drivers/clk/samsung/clk-s3c2410-dclk.c b/drivers/clk/samsung/clk-s3c2410-dclk.c index 0449cc0458e..f4f29ed6bd2 100644 --- a/drivers/clk/samsung/clk-s3c2410-dclk.c +++ b/drivers/clk/samsung/clk-s3c2410-dclk.c @@ -426,7 +426,6 @@ MODULE_DEVICE_TABLE(platform, s3c24xx_dclk_driver_ids); static struct platform_driver s3c24xx_dclk_driver = { .driver = { .name = "s3c24xx-dclk", - .owner = THIS_MODULE, .pm = &s3c24xx_dclk_pm_ops, }, .probe = s3c24xx_dclk_probe, diff --git a/drivers/clk/samsung/clk-s3c2412.c b/drivers/clk/samsung/clk-s3c2412.c index 34af09f6a15..2ceedaf8ce1 100644 --- a/drivers/clk/samsung/clk-s3c2412.c +++ b/drivers/clk/samsung/clk-s3c2412.c @@ -14,6 +14,7 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/syscore_ops.h> +#include <linux/reboot.h> #include <dt-bindings/clock/s3c2412.h> @@ -26,6 +27,7 @@ #define CLKCON 0x0c #define CLKDIVN 0x14 #define CLKSRC 0x1c +#define SWRST 0x30 /* list of PLLs to be registered */ enum s3c2412_plls { @@ -204,6 +206,28 @@ struct samsung_clock_alias s3c2412_aliases[] __initdata = { ALIAS(MSYSCLK, NULL, "fclk"), }; +static int s3c2412_restart(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + /* errata "Watch-dog/Software Reset Problem" specifies that + * this reset must be done with the SYSCLK sourced from + * EXTCLK instead of FOUT to avoid a glitch in the reset + * mechanism. + * + * See the watchdog section of the S3C2412 manual for more + * information on this fix. + */ + + __raw_writel(0x00, reg_base + CLKSRC); + __raw_writel(0x533C2412, reg_base + SWRST); + return NOTIFY_DONE; +} + +static struct notifier_block s3c2412_restart_handler = { + .notifier_call = s3c2412_restart, + .priority = 129, +}; + /* * fixed rate clocks generated outside the soc * Only necessary until the devicetree-move is complete @@ -233,6 +257,7 @@ void __init s3c2412_common_clk_init(struct device_node *np, unsigned long xti_f, unsigned long ext_f, void __iomem *base) { struct samsung_clk_provider *ctx; + int ret; reg_base = base; if (np) { @@ -267,6 +292,10 @@ void __init s3c2412_common_clk_init(struct device_node *np, unsigned long xti_f, s3c2412_clk_sleep_init(); samsung_clk_of_add_provider(np, ctx); + + ret = register_restart_handler(&s3c2412_restart_handler); + if (ret) + pr_warn("cannot register restart handler, %d\n", ret); } static void __init s3c2412_clk_init(struct device_node *np) diff --git a/drivers/clk/samsung/clk-s3c2443.c b/drivers/clk/samsung/clk-s3c2443.c index c92f853fca9..0c3c182b902 100644 --- a/drivers/clk/samsung/clk-s3c2443.c +++ b/drivers/clk/samsung/clk-s3c2443.c @@ -14,6 +14,7 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/syscore_ops.h> +#include <linux/reboot.h> #include <dt-bindings/clock/s3c2443.h> @@ -33,6 +34,7 @@ #define HCLKCON 0x30 #define PCLKCON 0x34 #define SCLKCON 0x38 +#define SWRST 0x44 /* the soc types */ enum supported_socs { @@ -354,6 +356,18 @@ struct samsung_clock_alias s3c2450_aliases[] __initdata = { ALIAS(PCLK_I2C1, "s3c2410-i2c.1", "i2c"), }; +static int s3c2443_restart(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + __raw_writel(0x533c2443, reg_base + SWRST); + return NOTIFY_DONE; +} + +static struct notifier_block s3c2443_restart_handler = { + .notifier_call = s3c2443_restart, + .priority = 129, +}; + /* * fixed rate clocks generated outside the soc * Only necessary until the devicetree-move is complete @@ -378,6 +392,7 @@ void __init s3c2443_common_clk_init(struct device_node *np, unsigned long xti_f, void __iomem *base) { struct samsung_clk_provider *ctx; + int ret; reg_base = base; if (np) { @@ -447,6 +462,10 @@ void __init s3c2443_common_clk_init(struct device_node *np, unsigned long xti_f, s3c2443_clk_sleep_init(); samsung_clk_of_add_provider(np, ctx); + + ret = register_restart_handler(&s3c2443_restart_handler); + if (ret) + pr_warn("cannot register restart handler, %d\n", ret); } static void __init s3c2416_clk_init(struct device_node *np) diff --git a/drivers/clk/shmobile/clk-rcar-gen2.c b/drivers/clk/shmobile/clk-rcar-gen2.c index dff7f79a19b..e996425d06a 100644 --- a/drivers/clk/shmobile/clk-rcar-gen2.c +++ b/drivers/clk/shmobile/clk-rcar-gen2.c @@ -202,6 +202,7 @@ static const struct clk_div_table cpg_sdh_div_table[] = { }; static const struct clk_div_table cpg_sd01_div_table[] = { + { 4, 8 }, { 5, 12 }, { 6, 16 }, { 7, 18 }, { 8, 24 }, { 10, 36 }, { 11, 48 }, { 12, 10 }, { 0, 0 }, }; diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index 6850cba3587..7ddc2b55384 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -5,6 +5,8 @@ obj-y += clk-sunxi.o clk-factors.o obj-y += clk-a10-hosc.o obj-y += clk-a20-gmac.o +obj-y += clk-mod0.o +obj-y += clk-sun8i-mbus.o obj-$(CONFIG_MFD_SUN6I_PRCM) += \ clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \ diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c index 2057c8ac648..f83ba097126 100644 --- a/drivers/clk/sunxi/clk-factors.c +++ b/drivers/clk/sunxi/clk-factors.c @@ -9,18 +9,18 @@ */ #include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> #include <linux/module.h> +#include <linux/of_address.h> #include <linux/slab.h> -#include <linux/io.h> -#include <linux/err.h> #include <linux/string.h> -#include <linux/delay.h> - #include "clk-factors.h" /* - * DOC: basic adjustable factor-based clock that cannot gate + * DOC: basic adjustable factor-based clock * * Traits of this clock: * prepare - clk_prepare only ensures that parents are prepared @@ -32,6 +32,8 @@ #define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw) +#define FACTORS_MAX_PARENTS 5 + #define SETMASK(len, pos) (((1U << (len)) - 1) << (pos)) #define CLRMASK(len, pos) (~(SETMASK(len, pos))) #define FACTOR_GET(bit, len, reg) (((reg) & SETMASK(len, bit)) >> (bit)) @@ -147,9 +149,96 @@ static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate, return 0; } -const struct clk_ops clk_factors_ops = { +static const struct clk_ops clk_factors_ops = { .determine_rate = clk_factors_determine_rate, .recalc_rate = clk_factors_recalc_rate, .round_rate = clk_factors_round_rate, .set_rate = clk_factors_set_rate, }; + +struct clk * __init sunxi_factors_register(struct device_node *node, + const struct factors_data *data, + spinlock_t *lock) +{ + struct clk *clk; + struct clk_factors *factors; + struct clk_gate *gate = NULL; + struct clk_mux *mux = NULL; + struct clk_hw *gate_hw = NULL; + struct clk_hw *mux_hw = NULL; + const char *clk_name = node->name; + const char *parents[FACTORS_MAX_PARENTS]; + void __iomem *reg; + int i = 0; + + reg = of_iomap(node, 0); + + /* if we have a mux, we will have >1 parents */ + while (i < FACTORS_MAX_PARENTS && + (parents[i] = of_clk_get_parent_name(node, i)) != NULL) + i++; + + /* + * some factor clocks, such as pll5 and pll6, may have multiple + * outputs, and have their name designated in factors_data + */ + if (data->name) + clk_name = data->name; + else + of_property_read_string(node, "clock-output-names", &clk_name); + + factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL); + if (!factors) + return NULL; + + /* set up factors properties */ + factors->reg = reg; + factors->config = data->table; + factors->get_factors = data->getter; + factors->lock = lock; + + /* Add a gate if this factor clock can be gated */ + if (data->enable) { + gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); + if (!gate) { + kfree(factors); + return NULL; + } + + /* set up gate properties */ + gate->reg = reg; + gate->bit_idx = data->enable; + gate->lock = factors->lock; + gate_hw = &gate->hw; + } + + /* Add a mux if this factor clock can be muxed */ + if (data->mux) { + mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL); + if (!mux) { + kfree(factors); + kfree(gate); + return NULL; + } + + /* set up gate properties */ + mux->reg = reg; + mux->shift = data->mux; + mux->mask = SUNXI_FACTORS_MUX_MASK; + mux->lock = factors->lock; + mux_hw = &mux->hw; + } + + clk = clk_register_composite(NULL, clk_name, + parents, i, + mux_hw, &clk_mux_ops, + &factors->hw, &clk_factors_ops, + gate_hw, &clk_gate_ops, 0); + + if (!IS_ERR(clk)) { + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + } + + return clk; +} diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h index d2d0efa3937..9913840018d 100644 --- a/drivers/clk/sunxi/clk-factors.h +++ b/drivers/clk/sunxi/clk-factors.h @@ -3,9 +3,12 @@ #include <linux/clk-provider.h> #include <linux/clkdev.h> +#include <linux/spinlock.h> #define SUNXI_FACTORS_NOT_APPLICABLE (0) +#define SUNXI_FACTORS_MUX_MASK 0x3 + struct clk_factors_config { u8 nshift; u8 nwidth; @@ -18,6 +21,14 @@ struct clk_factors_config { u8 n_start; }; +struct factors_data { + int enable; + int mux; + struct clk_factors_config *table; + void (*getter) (u32 *rate, u32 parent_rate, u8 *n, u8 *k, u8 *m, u8 *p); + const char *name; +}; + struct clk_factors { struct clk_hw hw; void __iomem *reg; @@ -26,5 +37,8 @@ struct clk_factors { spinlock_t *lock; }; -extern const struct clk_ops clk_factors_ops; +struct clk * __init sunxi_factors_register(struct device_node *node, + const struct factors_data *data, + spinlock_t *lock); + #endif diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c new file mode 100644 index 00000000000..4a563850ee6 --- /dev/null +++ b/drivers/clk/sunxi/clk-mod0.c @@ -0,0 +1,283 @@ +/* + * Copyright 2013 Emilio LĂłpez + * + * Emilio LĂłpez <emilio@elopez.com.ar> + * + * 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 <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> + +#include "clk-factors.h" + +/** + * sun4i_get_mod0_factors() - calculates m, n factors for MOD0-style clocks + * MOD0 rate is calculated as follows + * rate = (parent_rate >> p) / (m + 1); + */ + +static void sun4i_a10_get_mod0_factors(u32 *freq, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 div, calcm, calcp; + + /* These clocks can only divide, so we will never be able to achieve + * frequencies higher than the parent frequency */ + if (*freq > parent_rate) + *freq = parent_rate; + + div = DIV_ROUND_UP(parent_rate, *freq); + + if (div < 16) + calcp = 0; + else if (div / 2 < 16) + calcp = 1; + else if (div / 4 < 16) + calcp = 2; + else + calcp = 3; + + calcm = DIV_ROUND_UP(div, 1 << calcp); + + *freq = (parent_rate >> calcp) / calcm; + + /* we were called to round the frequency, we can now return */ + if (n == NULL) + return; + + *m = calcm - 1; + *p = calcp; +} + +/* user manual says "n" but it's really "p" */ +static struct clk_factors_config sun4i_a10_mod0_config = { + .mshift = 0, + .mwidth = 4, + .pshift = 16, + .pwidth = 2, +}; + +static const struct factors_data sun4i_a10_mod0_data __initconst = { + .enable = 31, + .mux = 24, + .table = &sun4i_a10_mod0_config, + .getter = sun4i_a10_get_mod0_factors, +}; + +static DEFINE_SPINLOCK(sun4i_a10_mod0_lock); + +static void __init sun4i_a10_mod0_setup(struct device_node *node) +{ + sunxi_factors_register(node, &sun4i_a10_mod0_data, &sun4i_a10_mod0_lock); +} +CLK_OF_DECLARE(sun4i_a10_mod0, "allwinner,sun4i-a10-mod0-clk", sun4i_a10_mod0_setup); + +static DEFINE_SPINLOCK(sun5i_a13_mbus_lock); + +static void __init sun5i_a13_mbus_setup(struct device_node *node) +{ + struct clk *mbus = sunxi_factors_register(node, &sun4i_a10_mod0_data, &sun5i_a13_mbus_lock); + + /* The MBUS clocks needs to be always enabled */ + __clk_get(mbus); + clk_prepare_enable(mbus); +} +CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup); + +struct mmc_phase_data { + u8 offset; +}; + +struct mmc_phase { + struct clk_hw hw; + void __iomem *reg; + struct mmc_phase_data *data; + spinlock_t *lock; +}; + +#define to_mmc_phase(_hw) container_of(_hw, struct mmc_phase, hw) + +static int mmc_get_phase(struct clk_hw *hw) +{ + struct clk *mmc, *mmc_parent, *clk = hw->clk; + struct mmc_phase *phase = to_mmc_phase(hw); + unsigned int mmc_rate, mmc_parent_rate; + u16 step, mmc_div; + u32 value; + u8 delay; + + value = readl(phase->reg); + delay = (value >> phase->data->offset) & 0x3; + + if (!delay) + return 180; + + /* Get the main MMC clock */ + mmc = clk_get_parent(clk); + if (!mmc) + return -EINVAL; + + /* And its rate */ + mmc_rate = clk_get_rate(mmc); + if (!mmc_rate) + return -EINVAL; + + /* Now, get the MMC parent (most likely some PLL) */ + mmc_parent = clk_get_parent(mmc); + if (!mmc_parent) + return -EINVAL; + + /* And its rate */ + mmc_parent_rate = clk_get_rate(mmc_parent); + if (!mmc_parent_rate) + return -EINVAL; + + /* Get MMC clock divider */ + mmc_div = mmc_parent_rate / mmc_rate; + + step = DIV_ROUND_CLOSEST(360, mmc_div); + return delay * step; +} + +static int mmc_set_phase(struct clk_hw *hw, int degrees) +{ + struct clk *mmc, *mmc_parent, *clk = hw->clk; + struct mmc_phase *phase = to_mmc_phase(hw); + unsigned int mmc_rate, mmc_parent_rate; + unsigned long flags; + u32 value; + u8 delay; + + /* Get the main MMC clock */ + mmc = clk_get_parent(clk); + if (!mmc) + return -EINVAL; + + /* And its rate */ + mmc_rate = clk_get_rate(mmc); + if (!mmc_rate) + return -EINVAL; + + /* Now, get the MMC parent (most likely some PLL) */ + mmc_parent = clk_get_parent(mmc); + if (!mmc_parent) + return -EINVAL; + + /* And its rate */ + mmc_parent_rate = clk_get_rate(mmc_parent); + if (!mmc_parent_rate) + return -EINVAL; + + if (degrees != 180) { + u16 step, mmc_div; + + /* Get MMC clock divider */ + mmc_div = mmc_parent_rate / mmc_rate; + + /* + * We can only outphase the clocks by multiple of the + * PLL's period. + * + * Since the MMC clock in only a divider, and the + * formula to get the outphasing in degrees is deg = + * 360 * delta / period + * + * If we simplify this formula, we can see that the + * only thing that we're concerned about is the number + * of period we want to outphase our clock from, and + * the divider set by the MMC clock. + */ + step = DIV_ROUND_CLOSEST(360, mmc_div); + delay = DIV_ROUND_CLOSEST(degrees, step); + } else { + delay = 0; + } + + spin_lock_irqsave(phase->lock, flags); + value = readl(phase->reg); + value &= ~GENMASK(phase->data->offset + 3, phase->data->offset); + value |= delay << phase->data->offset; + writel(value, phase->reg); + spin_unlock_irqrestore(phase->lock, flags); + + return 0; +} + +static const struct clk_ops mmc_clk_ops = { + .get_phase = mmc_get_phase, + .set_phase = mmc_set_phase, +}; + +static void __init sun4i_a10_mmc_phase_setup(struct device_node *node, + struct mmc_phase_data *data) +{ + const char *parent_names[1] = { of_clk_get_parent_name(node, 0) }; + struct clk_init_data init = { + .num_parents = 1, + .parent_names = parent_names, + .ops = &mmc_clk_ops, + }; + + struct mmc_phase *phase; + struct clk *clk; + + phase = kmalloc(sizeof(*phase), GFP_KERNEL); + if (!phase) + return; + + phase->hw.init = &init; + + phase->reg = of_iomap(node, 0); + if (!phase->reg) + goto err_free; + + phase->data = data; + phase->lock = &sun4i_a10_mod0_lock; + + if (of_property_read_string(node, "clock-output-names", &init.name)) + init.name = node->name; + + clk = clk_register(NULL, &phase->hw); + if (IS_ERR(clk)) + goto err_unmap; + + of_clk_add_provider(node, of_clk_src_simple_get, clk); + + return; + +err_unmap: + iounmap(phase->reg); +err_free: + kfree(phase); +} + + +static struct mmc_phase_data mmc_output_clk = { + .offset = 8, +}; + +static struct mmc_phase_data mmc_sample_clk = { + .offset = 20, +}; + +static void __init sun4i_a10_mmc_output_setup(struct device_node *node) +{ + sun4i_a10_mmc_phase_setup(node, &mmc_output_clk); +} +CLK_OF_DECLARE(sun4i_a10_mmc_output, "allwinner,sun4i-a10-mmc-output-clk", sun4i_a10_mmc_output_setup); + +static void __init sun4i_a10_mmc_sample_setup(struct device_node *node) +{ + sun4i_a10_mmc_phase_setup(node, &mmc_sample_clk); +} +CLK_OF_DECLARE(sun4i_a10_mmc_sample, "allwinner,sun4i-a10-mmc-sample-clk", sun4i_a10_mmc_sample_setup); diff --git a/drivers/clk/sunxi/clk-sun6i-apb0-gates.c b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c index e10d0521ec7..64f3e46d383 100644 --- a/drivers/clk/sunxi/clk-sun6i-apb0-gates.c +++ b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c @@ -99,7 +99,6 @@ static int sun6i_a31_apb0_gates_clk_probe(struct platform_device *pdev) static struct platform_driver sun6i_a31_apb0_gates_clk_driver = { .driver = { .name = "sun6i-a31-apb0-gates-clk", - .owner = THIS_MODULE, .of_match_table = sun6i_a31_apb0_gates_clk_dt_ids, }, .probe = sun6i_a31_apb0_gates_clk_probe, diff --git a/drivers/clk/sunxi/clk-sun6i-apb0.c b/drivers/clk/sunxi/clk-sun6i-apb0.c index 1fa23371c8c..70763600aea 100644 --- a/drivers/clk/sunxi/clk-sun6i-apb0.c +++ b/drivers/clk/sunxi/clk-sun6i-apb0.c @@ -65,7 +65,6 @@ static const struct of_device_id sun6i_a31_apb0_clk_dt_ids[] = { static struct platform_driver sun6i_a31_apb0_clk_driver = { .driver = { .name = "sun6i-a31-apb0-clk", - .owner = THIS_MODULE, .of_match_table = sun6i_a31_apb0_clk_dt_ids, }, .probe = sun6i_a31_apb0_clk_probe, diff --git a/drivers/clk/sunxi/clk-sun6i-ar100.c b/drivers/clk/sunxi/clk-sun6i-ar100.c index eca8ca025b6..acca53290be 100644 --- a/drivers/clk/sunxi/clk-sun6i-ar100.c +++ b/drivers/clk/sunxi/clk-sun6i-ar100.c @@ -221,7 +221,6 @@ static const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = { static struct platform_driver sun6i_a31_ar100_clk_driver = { .driver = { .name = "sun6i-a31-ar100-clk", - .owner = THIS_MODULE, .of_match_table = sun6i_a31_ar100_clk_dt_ids, }, .probe = sun6i_a31_ar100_clk_probe, diff --git a/drivers/clk/sunxi/clk-sun8i-apb0.c b/drivers/clk/sunxi/clk-sun8i-apb0.c index 1f5ba9b4b8c..155d0022194 100644 --- a/drivers/clk/sunxi/clk-sun8i-apb0.c +++ b/drivers/clk/sunxi/clk-sun8i-apb0.c @@ -56,7 +56,6 @@ static const struct of_device_id sun8i_a23_apb0_clk_dt_ids[] = { static struct platform_driver sun8i_a23_apb0_clk_driver = { .driver = { .name = "sun8i-a23-apb0-clk", - .owner = THIS_MODULE, .of_match_table = sun8i_a23_apb0_clk_dt_ids, }, .probe = sun8i_a23_apb0_clk_probe, diff --git a/drivers/clk/sunxi/clk-sun8i-mbus.c b/drivers/clk/sunxi/clk-sun8i-mbus.c new file mode 100644 index 00000000000..8e49b44cee4 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun8i-mbus.c @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Chen-Yu Tsai + * + * Chen-Yu Tsai <wens@csie.org> + * + * 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 <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/of_address.h> + +#include "clk-factors.h" + +/** + * sun8i_a23_get_mbus_factors() - calculates m factor for MBUS clocks + * MBUS rate is calculated as follows + * rate = parent_rate / (m + 1); + */ + +static void sun8i_a23_get_mbus_factors(u32 *freq, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 div; + + /* + * These clocks can only divide, so we will never be able to + * achieve frequencies higher than the parent frequency + */ + if (*freq > parent_rate) + *freq = parent_rate; + + div = DIV_ROUND_UP(parent_rate, *freq); + + if (div > 8) + div = 8; + + *freq = parent_rate / div; + + /* we were called to round the frequency, we can now return */ + if (m == NULL) + return; + + *m = div - 1; +} + +static struct clk_factors_config sun8i_a23_mbus_config = { + .mshift = 0, + .mwidth = 3, +}; + +static const struct factors_data sun8i_a23_mbus_data __initconst = { + .enable = 31, + .mux = 24, + .table = &sun8i_a23_mbus_config, + .getter = sun8i_a23_get_mbus_factors, +}; + +static DEFINE_SPINLOCK(sun8i_a23_mbus_lock); + +static void __init sun8i_a23_mbus_setup(struct device_node *node) +{ + struct clk *mbus = sunxi_factors_register(node, &sun8i_a23_mbus_data, + &sun8i_a23_mbus_lock); + + /* The MBUS clocks needs to be always enabled */ + __clk_get(mbus); + clk_prepare_enable(mbus); +} +CLK_OF_DECLARE(sun8i_a23_mbus, "allwinner,sun8i-a23-mbus-clk", sun8i_a23_mbus_setup); diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index b654b7b1d13..d5dc951264c 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -19,6 +19,7 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/reset-controller.h> +#include <linux/spinlock.h> #include "clk-factors.h" @@ -319,46 +320,6 @@ static void sun4i_get_apb1_factors(u32 *freq, u32 parent_rate, -/** - * sun4i_get_mod0_factors() - calculates m, n factors for MOD0-style clocks - * MOD0 rate is calculated as follows - * rate = (parent_rate >> p) / (m + 1); - */ - -static void sun4i_get_mod0_factors(u32 *freq, u32 parent_rate, - u8 *n, u8 *k, u8 *m, u8 *p) -{ - u8 div, calcm, calcp; - - /* These clocks can only divide, so we will never be able to achieve - * frequencies higher than the parent frequency */ - if (*freq > parent_rate) - *freq = parent_rate; - - div = DIV_ROUND_UP(parent_rate, *freq); - - if (div < 16) - calcp = 0; - else if (div / 2 < 16) - calcp = 1; - else if (div / 4 < 16) - calcp = 2; - else - calcp = 3; - - calcm = DIV_ROUND_UP(div, 1 << calcp); - - *freq = (parent_rate >> calcp) / calcm; - - /* we were called to round the frequency, we can now return */ - if (n == NULL) - return; - - *m = calcm - 1; - *p = calcp; -} - - /** * sun7i_a20_get_out_factors() - calculates m, p factors for CLK_OUT_A/B @@ -440,16 +401,6 @@ EXPORT_SYMBOL(clk_sunxi_mmc_phase_control); * sunxi_factors_clk_setup() - Setup function for factor clocks */ -#define SUNXI_FACTORS_MUX_MASK 0x3 - -struct factors_data { - int enable; - int mux; - struct clk_factors_config *table; - void (*getter) (u32 *rate, u32 parent_rate, u8 *n, u8 *k, u8 *m, u8 *p); - const char *name; -}; - static struct clk_factors_config sun4i_pll1_config = { .nshift = 8, .nwidth = 5, @@ -504,14 +455,6 @@ static struct clk_factors_config sun4i_apb1_config = { }; /* user manual says "n" but it's really "p" */ -static struct clk_factors_config sun4i_mod0_config = { - .mshift = 0, - .mwidth = 4, - .pshift = 16, - .pwidth = 2, -}; - -/* user manual says "n" but it's really "p" */ static struct clk_factors_config sun7i_a20_out_config = { .mshift = 8, .mwidth = 5, @@ -568,13 +511,6 @@ static const struct factors_data sun4i_apb1_data __initconst = { .getter = sun4i_get_apb1_factors, }; -static const struct factors_data sun4i_mod0_data __initconst = { - .enable = 31, - .mux = 24, - .table = &sun4i_mod0_config, - .getter = sun4i_get_mod0_factors, -}; - static const struct factors_data sun7i_a20_out_data __initconst = { .enable = 31, .mux = 24, @@ -583,89 +519,9 @@ static const struct factors_data sun7i_a20_out_data __initconst = { }; static struct clk * __init sunxi_factors_clk_setup(struct device_node *node, - const struct factors_data *data) + const struct factors_data *data) { - struct clk *clk; - struct clk_factors *factors; - struct clk_gate *gate = NULL; - struct clk_mux *mux = NULL; - struct clk_hw *gate_hw = NULL; - struct clk_hw *mux_hw = NULL; - const char *clk_name = node->name; - const char *parents[SUNXI_MAX_PARENTS]; - void __iomem *reg; - int i = 0; - - reg = of_iomap(node, 0); - - /* if we have a mux, we will have >1 parents */ - while (i < SUNXI_MAX_PARENTS && - (parents[i] = of_clk_get_parent_name(node, i)) != NULL) - i++; - - /* - * some factor clocks, such as pll5 and pll6, may have multiple - * outputs, and have their name designated in factors_data - */ - if (data->name) - clk_name = data->name; - else - of_property_read_string(node, "clock-output-names", &clk_name); - - factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL); - if (!factors) - return NULL; - - /* Add a gate if this factor clock can be gated */ - if (data->enable) { - gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); - if (!gate) { - kfree(factors); - return NULL; - } - - /* set up gate properties */ - gate->reg = reg; - gate->bit_idx = data->enable; - gate->lock = &clk_lock; - gate_hw = &gate->hw; - } - - /* Add a mux if this factor clock can be muxed */ - if (data->mux) { - mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL); - if (!mux) { - kfree(factors); - kfree(gate); - return NULL; - } - - /* set up gate properties */ - mux->reg = reg; - mux->shift = data->mux; - mux->mask = SUNXI_FACTORS_MUX_MASK; - mux->lock = &clk_lock; - mux_hw = &mux->hw; - } - - /* set up factors properties */ - factors->reg = reg; - factors->config = data->table; - factors->get_factors = data->getter; - factors->lock = &clk_lock; - - clk = clk_register_composite(NULL, clk_name, - parents, i, - mux_hw, &clk_mux_ops, - &factors->hw, &clk_factors_ops, - gate_hw, &clk_gate_ops, 0); - - if (!IS_ERR(clk)) { - of_clk_add_provider(node, of_clk_src_simple_get, clk); - clk_register_clkdev(clk, clk_name, NULL); - } - - return clk; + return sunxi_factors_register(node, data, &clk_lock); } @@ -762,10 +618,19 @@ static const struct div_data sun4i_ahb_data __initconst = { .width = 2, }; +static const struct clk_div_table sun4i_apb0_table[] __initconst = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 8 }, + { } /* sentinel */ +}; + static const struct div_data sun4i_apb0_data __initconst = { .shift = 8, .pow = 1, .width = 2, + .table = sun4i_apb0_table, }; static const struct div_data sun6i_a31_apb2_div_data __initconst = { @@ -1199,7 +1064,6 @@ static const struct of_device_id clk_factors_match[] __initconst = { {.compatible = "allwinner,sun7i-a20-pll4-clk", .data = &sun7i_a20_pll4_data,}, {.compatible = "allwinner,sun6i-a31-pll6-clk", .data = &sun6i_a31_pll6_data,}, {.compatible = "allwinner,sun4i-a10-apb1-clk", .data = &sun4i_apb1_data,}, - {.compatible = "allwinner,sun4i-a10-mod0-clk", .data = &sun4i_mod0_data,}, {.compatible = "allwinner,sun7i-a20-out-clk", .data = &sun7i_a20_out_data,}, {} }; @@ -1311,7 +1175,6 @@ static void __init sun4i_a10_init_clocks(struct device_node *node) CLK_OF_DECLARE(sun4i_a10_clk_init, "allwinner,sun4i-a10", sun4i_a10_init_clocks); static const char *sun5i_critical_clocks[] __initdata = { - "mbus", "pll5_ddr", "ahb_sdram", }; diff --git a/drivers/clk/tegra/clk-tegra124.c b/drivers/clk/tegra/clk-tegra124.c index 9525c684d14..e3a85842ce0 100644 --- a/drivers/clk/tegra/clk-tegra124.c +++ b/drivers/clk/tegra/clk-tegra124.c @@ -1166,6 +1166,12 @@ static void __init tegra124_pll_init(void __iomem *clk_base, clk_register_clkdev(clk, "pll_c_out1", NULL); clks[TEGRA124_CLK_PLL_C_OUT1] = clk; + /* PLLC_UD */ + clk = clk_register_fixed_factor(NULL, "pll_c_ud", "pll_c", + CLK_SET_RATE_PARENT, 1, 1); + clk_register_clkdev(clk, "pll_c_ud", NULL); + clks[TEGRA124_CLK_PLL_C_UD] = clk; + /* PLLC2 */ clk = tegra_clk_register_pllc("pll_c2", "pll_ref", clk_base, pmc, 0, &pll_c2_params, NULL); @@ -1198,6 +1204,8 @@ static void __init tegra124_pll_init(void __iomem *clk_base, /* PLLM_UD */ clk = clk_register_fixed_factor(NULL, "pll_m_ud", "pll_m", CLK_SET_RATE_PARENT, 1, 1); + clk_register_clkdev(clk, "pll_m_ud", NULL); + clks[TEGRA124_CLK_PLL_M_UD] = clk; /* PLLU */ val = readl(clk_base + pll_u_params.base_reg); diff --git a/drivers/clk/tegra/clk.c b/drivers/clk/tegra/clk.c index f87c609e8f7..97dc8595c3c 100644 --- a/drivers/clk/tegra/clk.c +++ b/drivers/clk/tegra/clk.c @@ -207,8 +207,13 @@ void __init tegra_init_from_table(struct tegra_clk_init_table *tbl, for (; tbl->clk_id < clk_max; tbl++) { clk = clks[tbl->clk_id]; - if (IS_ERR_OR_NULL(clk)) - return; + if (IS_ERR_OR_NULL(clk)) { + pr_err("%s: invalid entry %ld in clks array for id %d\n", + __func__, PTR_ERR(clk), tbl->clk_id); + WARN_ON(1); + + continue; + } if (tbl->parent_id < clk_max) { struct clk *parent = clks[tbl->parent_id]; diff --git a/drivers/clk/ti/clk-dra7-atl.c b/drivers/clk/ti/clk-dra7-atl.c index af29359677d..59bb4b39d12 100644 --- a/drivers/clk/ti/clk-dra7-atl.c +++ b/drivers/clk/ti/clk-dra7-atl.c @@ -203,6 +203,7 @@ static void __init of_dra7_atl_clock_setup(struct device_node *node) if (!IS_ERR(clk)) { of_clk_add_provider(node, of_clk_src_simple_get, clk); + kfree(parent_names); return; } cleanup: @@ -228,6 +229,7 @@ static int of_dra7_atl_clk_probe(struct platform_device *pdev) cinfo->iobase = of_iomap(node, 0); cinfo->dev = &pdev->dev; pm_runtime_enable(cinfo->dev); + pm_runtime_irq_safe(cinfo->dev); pm_runtime_get_sync(cinfo->dev); atl_write(cinfo, DRA7_ATL_PCLKMUX_REG(0), DRA7_ATL_PCLKMUX); @@ -301,7 +303,6 @@ MODULE_DEVICE_TABLE(of, of_dra7_atl_clk_match_tbl); static struct platform_driver dra7_atl_clk_driver = { .driver = { .name = "dra7-atl", - .owner = THIS_MODULE, .of_match_table = of_dra7_atl_clk_match_tbl, }, .probe = of_dra7_atl_clk_probe, diff --git a/drivers/clk/ti/clk.c b/drivers/clk/ti/clk.c index b1a6f7144f3..337abe5909e 100644 --- a/drivers/clk/ti/clk.c +++ b/drivers/clk/ti/clk.c @@ -25,8 +25,8 @@ #undef pr_fmt #define pr_fmt(fmt) "%s: " fmt, __func__ -static int ti_dt_clk_memmap_index; struct ti_clk_ll_ops *ti_clk_ll_ops; +static struct device_node *clocks_node_ptr[CLK_MAX_MEMMAPS]; /** * ti_dt_clocks_register - register DT alias clocks during boot @@ -108,9 +108,21 @@ void __iomem *ti_clk_get_reg_addr(struct device_node *node, int index) struct clk_omap_reg *reg; u32 val; u32 tmp; + int i; reg = (struct clk_omap_reg *)&tmp; - reg->index = ti_dt_clk_memmap_index; + + for (i = 0; i < CLK_MAX_MEMMAPS; i++) { + if (clocks_node_ptr[i] == node->parent) + break; + } + + if (i == CLK_MAX_MEMMAPS) { + pr_err("clk-provider not found for %s!\n", node->name); + return NULL; + } + + reg->index = i; if (of_property_read_u32_index(node, "reg", index, &val)) { pr_err("%s must have reg[%d]!\n", node->name, index); @@ -127,20 +139,14 @@ void __iomem *ti_clk_get_reg_addr(struct device_node *node, int index) * @parent: master node * @index: internal index for clk_reg_ops * - * Initializes a master clock IP block and its child clock nodes. - * Regmap is provided for accessing the register space for the - * IP block and all the clocks under it. + * Initializes a master clock IP block. This basically sets up the + * mapping from clocks node to the memory map index. All the clocks + * are then initialized through the common of_clk_init call, and the + * clocks will access their memory maps based on the node layout. */ void ti_dt_clk_init_provider(struct device_node *parent, int index) { - const struct of_device_id *match; - struct device_node *np; struct device_node *clocks; - of_clk_init_cb_t clk_init_cb; - struct clk_init_item *retry; - struct clk_init_item *tmp; - - ti_dt_clk_memmap_index = index; /* get clocks for this parent */ clocks = of_get_child_by_name(parent, "clocks"); @@ -149,19 +155,31 @@ void ti_dt_clk_init_provider(struct device_node *parent, int index) return; } - for_each_child_of_node(clocks, np) { - match = of_match_node(&__clk_of_table, np); - if (!match) - continue; - clk_init_cb = (of_clk_init_cb_t)match->data; - pr_debug("%s: initializing: %s\n", __func__, np->name); - clk_init_cb(np); - } + /* add clocks node info */ + clocks_node_ptr[index] = clocks; +} - list_for_each_entry_safe(retry, tmp, &retry_list, link) { - pr_debug("retry-init: %s\n", retry->node->name); - retry->func(retry->hw, retry->node); - list_del(&retry->link); - kfree(retry); +/** + * ti_dt_clk_init_retry_clks - init clocks from the retry list + * + * Initializes any clocks that have failed to initialize before, + * reasons being missing parent node(s) during earlier init. This + * typically happens only for DPLLs which need to have both of their + * parent clocks ready during init. + */ +void ti_dt_clk_init_retry_clks(void) +{ + struct clk_init_item *retry; + struct clk_init_item *tmp; + int retries = 5; + + while (!list_empty(&retry_list) && retries) { + list_for_each_entry_safe(retry, tmp, &retry_list, link) { + pr_debug("retry-init: %s\n", retry->node->name); + retry->func(retry->hw, retry->node); + list_del(&retry->link); + kfree(retry); + } + retries--; } } diff --git a/drivers/clk/ti/clockdomain.c b/drivers/clk/ti/clockdomain.c index f1e0038d76a..b4c5faccaec 100644 --- a/drivers/clk/ti/clockdomain.c +++ b/drivers/clk/ti/clockdomain.c @@ -36,6 +36,11 @@ static void __init of_ti_clockdomain_setup(struct device_node *node) for (i = 0; i < num_clks; i++) { clk = of_clk_get(node, i); + if (IS_ERR(clk)) { + pr_err("%s: Failed get %s' clock nr %d (%ld)\n", + __func__, node->full_name, i, PTR_ERR(clk)); + continue; + } if (__clk_get_flags(clk) & CLK_IS_BASIC) { pr_warn("can't setup clkdm for basic clk %s\n", __clk_get_name(clk)); diff --git a/drivers/clk/ti/divider.c b/drivers/clk/ti/divider.c index a837f703be6..bff2b5b8ff5 100644 --- a/drivers/clk/ti/divider.c +++ b/drivers/clk/ti/divider.c @@ -300,8 +300,8 @@ static struct clk *_register_divider(struct device *dev, const char *name, return clk; } -static struct clk_div_table -__init *ti_clk_get_div_table(struct device_node *node) +static struct clk_div_table * +__init ti_clk_get_div_table(struct device_node *node) { struct clk_div_table *table; const __be32 *divspec; diff --git a/drivers/clk/zynq/clkc.c b/drivers/clk/zynq/clkc.c index 246cf1226ea..9037bebd69f 100644 --- a/drivers/clk/zynq/clkc.c +++ b/drivers/clk/zynq/clkc.c @@ -85,24 +85,22 @@ static DEFINE_SPINLOCK(canmioclk_lock); static DEFINE_SPINLOCK(dbgclk_lock); static DEFINE_SPINLOCK(aperclk_lock); -static const char dummy_nm[] __initconst = "dummy_name"; - -static const char *armpll_parents[] __initdata = {"armpll_int", "ps_clk"}; -static const char *ddrpll_parents[] __initdata = {"ddrpll_int", "ps_clk"}; -static const char *iopll_parents[] __initdata = {"iopll_int", "ps_clk"}; -static const char *gem0_mux_parents[] __initdata = {"gem0_div1", dummy_nm}; -static const char *gem1_mux_parents[] __initdata = {"gem1_div1", dummy_nm}; -static const char *can0_mio_mux2_parents[] __initdata = {"can0_gate", +static const char *armpll_parents[] __initconst = {"armpll_int", "ps_clk"}; +static const char *ddrpll_parents[] __initconst = {"ddrpll_int", "ps_clk"}; +static const char *iopll_parents[] __initconst = {"iopll_int", "ps_clk"}; +static const char *gem0_mux_parents[] __initconst = {"gem0_div1", "dummy_name"}; +static const char *gem1_mux_parents[] __initconst = {"gem1_div1", "dummy_name"}; +static const char *can0_mio_mux2_parents[] __initconst = {"can0_gate", "can0_mio_mux"}; -static const char *can1_mio_mux2_parents[] __initdata = {"can1_gate", +static const char *can1_mio_mux2_parents[] __initconst = {"can1_gate", "can1_mio_mux"}; -static const char *dbg_emio_mux_parents[] __initdata = {"dbg_div", - dummy_nm}; +static const char *dbg_emio_mux_parents[] __initconst = {"dbg_div", + "dummy_name"}; -static const char *dbgtrc_emio_input_names[] __initdata = {"trace_emio_clk"}; -static const char *gem0_emio_input_names[] __initdata = {"gem0_emio_clk"}; -static const char *gem1_emio_input_names[] __initdata = {"gem1_emio_clk"}; -static const char *swdt_ext_clk_input_names[] __initdata = {"swdt_ext_clk"}; +static const char *dbgtrc_emio_input_names[] __initconst = {"trace_emio_clk"}; +static const char *gem0_emio_input_names[] __initconst = {"gem0_emio_clk"}; +static const char *gem1_emio_input_names[] __initconst = {"gem1_emio_clk"}; +static const char *swdt_ext_clk_input_names[] __initconst = {"swdt_ext_clk"}; static void __init zynq_clk_register_fclk(enum zynq_clk fclk, const char *clk_name, void __iomem *fclk_ctrl_reg, @@ -230,6 +228,7 @@ static void __init zynq_clk_setup(struct device_node *np) const char *periph_parents[4]; const char *swdt_ext_clk_mux_parents[2]; const char *can_mio_mux_parents[NUM_MIO_PINS]; + const char *dummy_nm = "dummy_name"; pr_info("Zynq clock init\n"); @@ -619,5 +618,4 @@ void __init zynq_clock_init(void) np_err: of_node_put(np); BUG(); - return; } diff --git a/drivers/clk/zynq/pll.c b/drivers/clk/zynq/pll.c index cec97596fe6..00d72fb5c03 100644 --- a/drivers/clk/zynq/pll.c +++ b/drivers/clk/zynq/pll.c @@ -211,10 +211,8 @@ struct clk *clk_register_zynq_pll(const char *name, const char *parent, }; pll = kmalloc(sizeof(*pll), GFP_KERNEL); - if (!pll) { - pr_err("%s: Could not allocate Zynq PLL clk.\n", __func__); + if (!pll) return ERR_PTR(-ENOMEM); - } /* Populate the struct */ pll->hw.init = &initd; diff --git a/drivers/cpufreq/kirkwood-cpufreq.c b/drivers/cpufreq/kirkwood-cpufreq.c index 37a480680cd..7906d4acfe4 100644 --- a/drivers/cpufreq/kirkwood-cpufreq.c +++ b/drivers/cpufreq/kirkwood-cpufreq.c @@ -12,7 +12,6 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/clk.h> -#include <linux/clk-provider.h> #include <linux/cpufreq.h> #include <linux/of_device.h> #include <linux/platform_device.h> @@ -39,8 +38,7 @@ static struct priv * - cpu clk * - ddr clk * - * The frequencies are set at runtime before registering this * - * table. + * The frequencies are set at runtime before registering this table. */ static struct cpufreq_frequency_table kirkwood_freq_table[] = { {0, STATE_CPU_FREQ, 0}, /* CPU uses cpuclk */ @@ -50,9 +48,7 @@ static struct cpufreq_frequency_table kirkwood_freq_table[] = { static unsigned int kirkwood_cpufreq_get_cpu_frequency(unsigned int cpu) { - if (__clk_is_enabled(priv.powersave_clk)) - return kirkwood_freq_table[1].frequency; - return kirkwood_freq_table[0].frequency; + return clk_get_rate(priv.powersave_clk) / 1000; } static int kirkwood_cpufreq_target(struct cpufreq_policy *policy, @@ -70,10 +66,10 @@ static int kirkwood_cpufreq_target(struct cpufreq_policy *policy, switch (state) { case STATE_CPU_FREQ: - clk_disable(priv.powersave_clk); + clk_set_parent(priv.powersave_clk, priv.cpu_clk); break; case STATE_DDR_FREQ: - clk_enable(priv.powersave_clk); + clk_set_parent(priv.powersave_clk, priv.ddr_clk); break; } @@ -150,7 +146,7 @@ static int kirkwood_cpufreq_probe(struct platform_device *pdev) err = PTR_ERR(priv.powersave_clk); goto out_ddr; } - clk_prepare(priv.powersave_clk); + clk_prepare_enable(priv.powersave_clk); of_node_put(np); np = NULL; diff --git a/drivers/i2c/busses/i2c-cros-ec-tunnel.c b/drivers/i2c/busses/i2c-cros-ec-tunnel.c index 05e033c9811..8ca5cbbcec9 100644 --- a/drivers/i2c/busses/i2c-cros-ec-tunnel.c +++ b/drivers/i2c/busses/i2c-cros-ec-tunnel.c @@ -16,6 +16,8 @@ #include <linux/platform_device.h> #include <linux/slab.h> +#define I2C_MAX_RETRIES 3 + /** * struct ec_i2c_device - Driver data for I2C tunnel * @@ -227,7 +229,7 @@ static int ec_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg i2c_msgs[], msg.indata = response; msg.insize = response_len; - result = bus->ec->cmd_xfer(bus->ec, &msg); + result = cros_ec_cmd_xfer(bus->ec, &msg); if (result < 0) goto exit; @@ -290,6 +292,7 @@ static int ec_i2c_probe(struct platform_device *pdev) bus->adap.algo_data = bus; bus->adap.dev.parent = &pdev->dev; bus->adap.dev.of_node = np; + bus->adap.retries = I2C_MAX_RETRIES; err = i2c_add_adapter(&bus->adap); if (err) { diff --git a/drivers/infiniband/hw/usnic/usnic_uiom.c b/drivers/infiniband/hw/usnic/usnic_uiom.c index 801a1d6937e..417de1f3296 100644 --- a/drivers/infiniband/hw/usnic/usnic_uiom.c +++ b/drivers/infiniband/hw/usnic/usnic_uiom.c @@ -507,7 +507,7 @@ int usnic_uiom_attach_dev_to_pd(struct usnic_uiom_pd *pd, struct device *dev) if (err) goto out_free_dev; - if (!iommu_domain_has_cap(pd->domain, IOMMU_CAP_CACHE_COHERENCY)) { + if (!iommu_capable(dev->bus, IOMMU_CAP_CACHE_COHERENCY)) { usnic_err("IOMMU of %s does not support cache coherency\n", dev_name(dev)); err = -EINVAL; diff --git a/drivers/input/keyboard/cros_ec_keyb.c b/drivers/input/keyboard/cros_ec_keyb.c index 72d3499bb02..5d773d20230 100644 --- a/drivers/input/keyboard/cros_ec_keyb.c +++ b/drivers/input/keyboard/cros_ec_keyb.c @@ -157,7 +157,7 @@ static int cros_ec_keyb_get_state(struct cros_ec_keyb *ckdev, uint8_t *kb_state) .insize = ckdev->cols, }; - return ckdev->ec->cmd_xfer(ckdev->ec, &msg); + return cros_ec_cmd_xfer(ckdev->ec, &msg); } static irqreturn_t cros_ec_keyb_irq(int irq, void *data) diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index ecb0109a536..505a9adac2d 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -88,6 +88,27 @@ int amd_iommu_max_glx_val = -1; static struct dma_map_ops amd_iommu_dma_ops; /* + * This struct contains device specific data for the IOMMU + */ +struct iommu_dev_data { + struct list_head list; /* For domain->dev_list */ + struct list_head dev_data_list; /* For global dev_data_list */ + struct list_head alias_list; /* Link alias-groups together */ + struct iommu_dev_data *alias_data;/* The alias dev_data */ + struct protection_domain *domain; /* Domain the device is bound to */ + u16 devid; /* PCI Device ID */ + bool iommu_v2; /* Device can make use of IOMMUv2 */ + bool passthrough; /* Default for device is pt_domain */ + struct { + bool enabled; + int qdep; + } ats; /* ATS state */ + bool pri_tlp; /* PASID TLB required for + PPR completions */ + u32 errata; /* Bitmap for errata to apply */ +}; + +/* * general struct to manage commands send to an IOMMU */ struct iommu_cmd { @@ -114,8 +135,9 @@ static struct iommu_dev_data *alloc_dev_data(u16 devid) if (!dev_data) return NULL; + INIT_LIST_HEAD(&dev_data->alias_list); + dev_data->devid = devid; - atomic_set(&dev_data->bind, 0); spin_lock_irqsave(&dev_data_list_lock, flags); list_add_tail(&dev_data->dev_data_list, &dev_data_list); @@ -260,17 +282,13 @@ static bool check_device(struct device *dev) return true; } -static int init_iommu_group(struct device *dev) +static void init_iommu_group(struct device *dev) { struct iommu_group *group; group = iommu_group_get_for_dev(dev); - - if (IS_ERR(group)) - return PTR_ERR(group); - - iommu_group_put(group); - return 0; + if (!IS_ERR(group)) + iommu_group_put(group); } static int __last_alias(struct pci_dev *pdev, u16 alias, void *data) @@ -340,7 +358,6 @@ static int iommu_init_device(struct device *dev) struct pci_dev *pdev = to_pci_dev(dev); struct iommu_dev_data *dev_data; u16 alias; - int ret; if (dev->archdata.iommu) return 0; @@ -362,12 +379,9 @@ static int iommu_init_device(struct device *dev) return -ENOTSUPP; } dev_data->alias_data = alias_data; - } - ret = init_iommu_group(dev); - if (ret) { - free_dev_data(dev_data); - return ret; + /* Add device to the alias_list */ + list_add(&dev_data->alias_list, &alias_data->alias_list); } if (pci_iommuv2_capable(pdev)) { @@ -455,6 +469,15 @@ int __init amd_iommu_init_devices(void) goto out_free; } + /* + * Initialize IOMMU groups only after iommu_init_device() has + * had a chance to populate any IVRS defined aliases. + */ + for_each_pci_dev(pdev) { + if (check_device(&pdev->dev)) + init_iommu_group(&pdev->dev); + } + return 0; out_free: @@ -1368,6 +1391,9 @@ static int iommu_map_page(struct protection_domain *dom, count = PAGE_SIZE_PTE_COUNT(page_size); pte = alloc_pte(dom, bus_addr, page_size, NULL, GFP_KERNEL); + if (!pte) + return -ENOMEM; + for (i = 0; i < count; ++i) if (IOMMU_PTE_PRESENT(pte[i])) return -EBUSY; @@ -2122,35 +2148,29 @@ static void do_detach(struct iommu_dev_data *dev_data) static int __attach_device(struct iommu_dev_data *dev_data, struct protection_domain *domain) { + struct iommu_dev_data *head, *entry; int ret; /* lock domain */ spin_lock(&domain->lock); - if (dev_data->alias_data != NULL) { - struct iommu_dev_data *alias_data = dev_data->alias_data; + head = dev_data; - /* Some sanity checks */ - ret = -EBUSY; - if (alias_data->domain != NULL && - alias_data->domain != domain) - goto out_unlock; + if (head->alias_data != NULL) + head = head->alias_data; - if (dev_data->domain != NULL && - dev_data->domain != domain) - goto out_unlock; + /* Now we have the root of the alias group, if any */ - /* Do real assignment */ - if (alias_data->domain == NULL) - do_attach(alias_data, domain); - - atomic_inc(&alias_data->bind); - } + ret = -EBUSY; + if (head->domain != NULL) + goto out_unlock; - if (dev_data->domain == NULL) - do_attach(dev_data, domain); + /* Attach alias group root */ + do_attach(head, domain); - atomic_inc(&dev_data->bind); + /* Attach other devices in the alias group */ + list_for_each_entry(entry, &head->alias_list, alias_list) + do_attach(entry, domain); ret = 0; @@ -2298,6 +2318,7 @@ static int attach_device(struct device *dev, */ static void __detach_device(struct iommu_dev_data *dev_data) { + struct iommu_dev_data *head, *entry; struct protection_domain *domain; unsigned long flags; @@ -2307,15 +2328,14 @@ static void __detach_device(struct iommu_dev_data *dev_data) spin_lock_irqsave(&domain->lock, flags); - if (dev_data->alias_data != NULL) { - struct iommu_dev_data *alias_data = dev_data->alias_data; + head = dev_data; + if (head->alias_data != NULL) + head = head->alias_data; - if (atomic_dec_and_test(&alias_data->bind)) - do_detach(alias_data); - } + list_for_each_entry(entry, &head->alias_list, alias_list) + do_detach(entry); - if (atomic_dec_and_test(&dev_data->bind)) - do_detach(dev_data); + do_detach(head); spin_unlock_irqrestore(&domain->lock, flags); @@ -2415,6 +2435,7 @@ static int device_change_notifier(struct notifier_block *nb, case BUS_NOTIFY_ADD_DEVICE: iommu_init_device(dev); + init_iommu_group(dev); /* * dev_data is still NULL and @@ -3158,7 +3179,6 @@ static void cleanup_domain(struct protection_domain *domain) entry = list_first_entry(&domain->dev_list, struct iommu_dev_data, list); __detach_device(entry); - atomic_set(&entry->bind, 0); } write_unlock_irqrestore(&amd_iommu_devtable_lock, flags); @@ -3384,20 +3404,20 @@ static phys_addr_t amd_iommu_iova_to_phys(struct iommu_domain *dom, return paddr; } -static int amd_iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool amd_iommu_capable(enum iommu_cap cap) { switch (cap) { case IOMMU_CAP_CACHE_COHERENCY: - return 1; + return true; case IOMMU_CAP_INTR_REMAP: - return irq_remapping_enabled; + return (irq_remapping_enabled == 1); } - return 0; + return false; } static const struct iommu_ops amd_iommu_ops = { + .capable = amd_iommu_capable, .domain_init = amd_iommu_domain_init, .domain_destroy = amd_iommu_domain_destroy, .attach_dev = amd_iommu_attach_device, @@ -3405,7 +3425,6 @@ static const struct iommu_ops amd_iommu_ops = { .map = amd_iommu_map, .unmap = amd_iommu_unmap, .iova_to_phys = amd_iommu_iova_to_phys, - .domain_has_cap = amd_iommu_domain_has_cap, .pgsize_bitmap = AMD_IOMMU_PGSIZES, }; @@ -4235,7 +4254,7 @@ static int msi_setup_irq(struct pci_dev *pdev, unsigned int irq, return 0; } -static int setup_hpet_msi(unsigned int irq, unsigned int id) +static int alloc_hpet_msi(unsigned int irq, unsigned int id) { struct irq_2_irte *irte_info; struct irq_cfg *cfg; @@ -4274,6 +4293,6 @@ struct irq_remap_ops amd_iommu_irq_ops = { .compose_msi_msg = compose_msi_msg, .msi_alloc_irq = msi_alloc_irq, .msi_setup_irq = msi_setup_irq, - .setup_hpet_msi = setup_hpet_msi, + .alloc_hpet_msi = alloc_hpet_msi, }; #endif diff --git a/drivers/iommu/amd_iommu_init.c b/drivers/iommu/amd_iommu_init.c index 3783e0b44df..b0522f15730 100644 --- a/drivers/iommu/amd_iommu_init.c +++ b/drivers/iommu/amd_iommu_init.c @@ -712,7 +712,7 @@ static void __init set_dev_entry_from_acpi(struct amd_iommu *iommu, set_iommu_for_device(iommu, devid); } -static int __init add_special_device(u8 type, u8 id, u16 devid, bool cmd_line) +static int __init add_special_device(u8 type, u8 id, u16 *devid, bool cmd_line) { struct devid_map *entry; struct list_head *list; @@ -731,6 +731,8 @@ static int __init add_special_device(u8 type, u8 id, u16 devid, bool cmd_line) pr_info("AMD-Vi: Command-line override present for %s id %d - ignoring\n", type == IVHD_SPECIAL_IOAPIC ? "IOAPIC" : "HPET", id); + *devid = entry->devid; + return 0; } @@ -739,7 +741,7 @@ static int __init add_special_device(u8 type, u8 id, u16 devid, bool cmd_line) return -ENOMEM; entry->id = id; - entry->devid = devid; + entry->devid = *devid; entry->cmd_line = cmd_line; list_add_tail(&entry->list, list); @@ -754,7 +756,7 @@ static int __init add_early_maps(void) for (i = 0; i < early_ioapic_map_size; ++i) { ret = add_special_device(IVHD_SPECIAL_IOAPIC, early_ioapic_map[i].id, - early_ioapic_map[i].devid, + &early_ioapic_map[i].devid, early_ioapic_map[i].cmd_line); if (ret) return ret; @@ -763,7 +765,7 @@ static int __init add_early_maps(void) for (i = 0; i < early_hpet_map_size; ++i) { ret = add_special_device(IVHD_SPECIAL_HPET, early_hpet_map[i].id, - early_hpet_map[i].devid, + &early_hpet_map[i].devid, early_hpet_map[i].cmd_line); if (ret) return ret; @@ -978,10 +980,17 @@ static int __init init_iommu_from_acpi(struct amd_iommu *iommu, PCI_SLOT(devid), PCI_FUNC(devid)); - set_dev_entry_from_acpi(iommu, devid, e->flags, 0); - ret = add_special_device(type, handle, devid, false); + ret = add_special_device(type, handle, &devid, false); if (ret) return ret; + + /* + * add_special_device might update the devid in case a + * command-line override is present. So call + * set_dev_entry_from_acpi after add_special_device. + */ + set_dev_entry_from_acpi(iommu, devid, e->flags, 0); + break; } default: diff --git a/drivers/iommu/amd_iommu_types.h b/drivers/iommu/amd_iommu_types.h index 8e43b7cba13..cec51a8ba84 100644 --- a/drivers/iommu/amd_iommu_types.h +++ b/drivers/iommu/amd_iommu_types.h @@ -418,27 +418,6 @@ struct protection_domain { }; /* - * This struct contains device specific data for the IOMMU - */ -struct iommu_dev_data { - struct list_head list; /* For domain->dev_list */ - struct list_head dev_data_list; /* For global dev_data_list */ - struct iommu_dev_data *alias_data;/* The alias dev_data */ - struct protection_domain *domain; /* Domain the device is bound to */ - atomic_t bind; /* Domain attach reference count */ - u16 devid; /* PCI Device ID */ - bool iommu_v2; /* Device can make use of IOMMUv2 */ - bool passthrough; /* Default for device is pt_domain */ - struct { - bool enabled; - int qdep; - } ats; /* ATS state */ - bool pri_tlp; /* PASID TLB required for - PPR completions */ - u32 errata; /* Bitmap for errata to apply */ -}; - -/* * For dynamic growth the aperture size is split into ranges of 128MB of * DMA address space each. This struct represents one such range. */ diff --git a/drivers/iommu/arm-smmu.c b/drivers/iommu/arm-smmu.c index a83cc2a2a2c..60558f79492 100644 --- a/drivers/iommu/arm-smmu.c +++ b/drivers/iommu/arm-smmu.c @@ -24,7 +24,7 @@ * - v7/v8 long-descriptor format * - Non-secure access to the SMMU * - 4k and 64k pages, with contiguous pte hints. - * - Up to 42-bit addressing (dependent on VA_BITS) + * - Up to 48-bit addressing (dependent on VA_BITS) * - Context fault reporting */ @@ -59,7 +59,7 @@ /* SMMU global address space */ #define ARM_SMMU_GR0(smmu) ((smmu)->base) -#define ARM_SMMU_GR1(smmu) ((smmu)->base + (smmu)->pagesize) +#define ARM_SMMU_GR1(smmu) ((smmu)->base + (1 << (smmu)->pgshift)) /* * SMMU global address space with conditional offset to access secure @@ -224,7 +224,7 @@ /* Translation context bank */ #define ARM_SMMU_CB_BASE(smmu) ((smmu)->base + ((smmu)->size >> 1)) -#define ARM_SMMU_CB(smmu, n) ((n) * (smmu)->pagesize) +#define ARM_SMMU_CB(smmu, n) ((n) * (1 << (smmu)->pgshift)) #define ARM_SMMU_CB_SCTLR 0x0 #define ARM_SMMU_CB_RESUME 0x8 @@ -326,6 +326,16 @@ #define FSYNR0_WNR (1 << 4) +static int force_stage; +module_param_named(force_stage, force_stage, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(force_stage, + "Force SMMU mappings to be installed at a particular stage of translation. A value of '1' or '2' forces the corresponding stage. All other values are ignored (i.e. no stage is forced). Note that selecting a specific stage will disable support for nested translation."); + +enum arm_smmu_arch_version { + ARM_SMMU_V1 = 1, + ARM_SMMU_V2, +}; + struct arm_smmu_smr { u8 idx; u16 mask; @@ -349,7 +359,7 @@ struct arm_smmu_device { void __iomem *base; unsigned long size; - unsigned long pagesize; + unsigned long pgshift; #define ARM_SMMU_FEAT_COHERENT_WALK (1 << 0) #define ARM_SMMU_FEAT_STREAM_MATCH (1 << 1) @@ -360,7 +370,7 @@ struct arm_smmu_device { #define ARM_SMMU_OPT_SECURE_CFG_ACCESS (1 << 0) u32 options; - int version; + enum arm_smmu_arch_version version; u32 num_context_banks; u32 num_s2_context_banks; @@ -370,8 +380,9 @@ struct arm_smmu_device { u32 num_mapping_groups; DECLARE_BITMAP(smr_map, ARM_SMMU_MAX_SMRS); - unsigned long input_size; + unsigned long s1_input_size; unsigned long s1_output_size; + unsigned long s2_input_size; unsigned long s2_output_size; u32 num_global_irqs; @@ -426,17 +437,17 @@ static void parse_driver_options(struct arm_smmu_device *smmu) } while (arm_smmu_options[++i].opt); } -static struct device *dev_get_master_dev(struct device *dev) +static struct device_node *dev_get_dev_node(struct device *dev) { if (dev_is_pci(dev)) { struct pci_bus *bus = to_pci_dev(dev)->bus; while (!pci_is_root_bus(bus)) bus = bus->parent; - return bus->bridge->parent; + return bus->bridge->parent->of_node; } - return dev; + return dev->of_node; } static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, @@ -461,15 +472,17 @@ static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu, } static struct arm_smmu_master_cfg * -find_smmu_master_cfg(struct arm_smmu_device *smmu, struct device *dev) +find_smmu_master_cfg(struct device *dev) { - struct arm_smmu_master *master; + struct arm_smmu_master_cfg *cfg = NULL; + struct iommu_group *group = iommu_group_get(dev); - if (dev_is_pci(dev)) - return dev->archdata.iommu; + if (group) { + cfg = iommu_group_get_iommudata(group); + iommu_group_put(group); + } - master = find_smmu_master(smmu, dev->of_node); - return master ? &master->cfg : NULL; + return cfg; } static int insert_smmu_master(struct arm_smmu_device *smmu, @@ -545,7 +558,7 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev) { struct arm_smmu_device *smmu; struct arm_smmu_master *master = NULL; - struct device_node *dev_node = dev_get_master_dev(dev)->of_node; + struct device_node *dev_node = dev_get_dev_node(dev); spin_lock(&arm_smmu_devices_lock); list_for_each_entry(smmu, &arm_smmu_devices, list) { @@ -729,7 +742,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) /* CBAR */ reg = cfg->cbar; - if (smmu->version == 1) + if (smmu->version == ARM_SMMU_V1) reg |= cfg->irptndx << CBAR_IRPTNDX_SHIFT; /* @@ -744,7 +757,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) } writel_relaxed(reg, gr1_base + ARM_SMMU_GR1_CBAR(cfg->cbndx)); - if (smmu->version > 1) { + if (smmu->version > ARM_SMMU_V1) { /* CBA2R */ #ifdef CONFIG_64BIT reg = CBA2R_RW64_64BIT; @@ -755,7 +768,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) gr1_base + ARM_SMMU_GR1_CBA2R(cfg->cbndx)); /* TTBCR2 */ - switch (smmu->input_size) { + switch (smmu->s1_input_size) { case 32: reg = (TTBCR2_ADDR_32 << TTBCR2_SEP_SHIFT); break; @@ -817,14 +830,14 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) * TTBCR * We use long descriptor, with inner-shareable WBWA tables in TTBR0. */ - if (smmu->version > 1) { + if (smmu->version > ARM_SMMU_V1) { if (PAGE_SIZE == SZ_4K) reg = TTBCR_TG0_4K; else reg = TTBCR_TG0_64K; if (!stage1) { - reg |= (64 - smmu->s1_output_size) << TTBCR_T0SZ_SHIFT; + reg |= (64 - smmu->s2_input_size) << TTBCR_T0SZ_SHIFT; switch (smmu->s2_output_size) { case 32: @@ -847,7 +860,7 @@ static void arm_smmu_init_context_bank(struct arm_smmu_domain *smmu_domain) break; } } else { - reg |= (64 - smmu->input_size) << TTBCR_T0SZ_SHIFT; + reg |= (64 - smmu->s1_input_size) << TTBCR_T0SZ_SHIFT; } } else { reg = 0; @@ -914,7 +927,7 @@ static int arm_smmu_init_domain_context(struct iommu_domain *domain, goto out_unlock; cfg->cbndx = ret; - if (smmu->version == 1) { + if (smmu->version == ARM_SMMU_V1) { cfg->irptndx = atomic_inc_return(&smmu->irptndx); cfg->irptndx %= smmu->num_context_irqs; } else { @@ -1151,9 +1164,10 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain, struct arm_smmu_device *smmu = smmu_domain->smmu; void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + /* Devices in an IOMMU group may already be configured */ ret = arm_smmu_master_configure_smrs(smmu, cfg); if (ret) - return ret; + return ret == -EEXIST ? 0 : ret; for (i = 0; i < cfg->num_streamids; ++i) { u32 idx, s2cr; @@ -1174,6 +1188,10 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain, struct arm_smmu_device *smmu = smmu_domain->smmu; void __iomem *gr0_base = ARM_SMMU_GR0(smmu); + /* An IOMMU group is torn down by the first device to be removed */ + if ((smmu->features & ARM_SMMU_FEAT_STREAM_MATCH) && !cfg->smrs) + return; + /* * We *must* clear the S2CR first, because freeing the SMR means * that it can be re-allocated immediately. @@ -1195,12 +1213,17 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_device *smmu, *dom_smmu; struct arm_smmu_master_cfg *cfg; - smmu = dev_get_master_dev(dev)->archdata.iommu; + smmu = find_smmu_for_device(dev); if (!smmu) { dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n"); return -ENXIO; } + if (dev->archdata.iommu) { + dev_err(dev, "already attached to IOMMU domain\n"); + return -EEXIST; + } + /* * Sanity check the domain. We don't support domains across * different SMMUs. @@ -1223,11 +1246,14 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev) } /* Looks ok, so add the device to the domain */ - cfg = find_smmu_master_cfg(smmu_domain->smmu, dev); + cfg = find_smmu_master_cfg(dev); if (!cfg) return -ENODEV; - return arm_smmu_domain_add_master(smmu_domain, cfg); + ret = arm_smmu_domain_add_master(smmu_domain, cfg); + if (!ret) + dev->archdata.iommu = domain; + return ret; } static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) @@ -1235,9 +1261,12 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev) struct arm_smmu_domain *smmu_domain = domain->priv; struct arm_smmu_master_cfg *cfg; - cfg = find_smmu_master_cfg(smmu_domain->smmu, dev); - if (cfg) - arm_smmu_domain_remove_master(smmu_domain, cfg); + cfg = find_smmu_master_cfg(dev); + if (!cfg) + return; + + dev->archdata.iommu = NULL; + arm_smmu_domain_remove_master(smmu_domain, cfg); } static bool arm_smmu_pte_is_contiguous_range(unsigned long addr, @@ -1379,6 +1408,7 @@ static int arm_smmu_alloc_init_pmd(struct arm_smmu_device *smmu, pud_t *pud, ret = arm_smmu_alloc_init_pte(smmu, pmd, addr, next, pfn, prot, stage); phys += next - addr; + pfn = __phys_to_pfn(phys); } while (pmd++, addr = next, addr < end); return ret; @@ -1431,9 +1461,11 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain, if (cfg->cbar == CBAR_TYPE_S2_TRANS) { stage = 2; + input_mask = (1ULL << smmu->s2_input_size) - 1; output_mask = (1ULL << smmu->s2_output_size) - 1; } else { stage = 1; + input_mask = (1ULL << smmu->s1_input_size) - 1; output_mask = (1ULL << smmu->s1_output_size) - 1; } @@ -1443,7 +1475,6 @@ static int arm_smmu_handle_mapping(struct arm_smmu_domain *smmu_domain, if (size & ~PAGE_MASK) return -EINVAL; - input_mask = (1ULL << smmu->input_size) - 1; if ((phys_addr_t)iova & ~input_mask) return -ERANGE; @@ -1526,20 +1557,19 @@ static phys_addr_t arm_smmu_iova_to_phys(struct iommu_domain *domain, return __pfn_to_phys(pte_pfn(pte)) | (iova & ~PAGE_MASK); } -static int arm_smmu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool arm_smmu_capable(enum iommu_cap cap) { - struct arm_smmu_domain *smmu_domain = domain->priv; - struct arm_smmu_device *smmu = smmu_domain->smmu; - u32 features = smmu ? smmu->features : 0; - switch (cap) { case IOMMU_CAP_CACHE_COHERENCY: - return features & ARM_SMMU_FEAT_COHERENT_WALK; + /* + * Return true here as the SMMU can always send out coherent + * requests. + */ + return true; case IOMMU_CAP_INTR_REMAP: - return 1; /* MSIs are just memory writes */ + return true; /* MSIs are just memory writes */ default: - return 0; + return false; } } @@ -1549,17 +1579,19 @@ static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data) return 0; /* Continue walking */ } +static void __arm_smmu_release_pci_iommudata(void *data) +{ + kfree(data); +} + static int arm_smmu_add_device(struct device *dev) { struct arm_smmu_device *smmu; + struct arm_smmu_master_cfg *cfg; struct iommu_group *group; + void (*releasefn)(void *) = NULL; int ret; - if (dev->archdata.iommu) { - dev_warn(dev, "IOMMU driver already assigned to device\n"); - return -EINVAL; - } - smmu = find_smmu_for_device(dev); if (!smmu) return -ENODEV; @@ -1571,7 +1603,6 @@ static int arm_smmu_add_device(struct device *dev) } if (dev_is_pci(dev)) { - struct arm_smmu_master_cfg *cfg; struct pci_dev *pdev = to_pci_dev(dev); cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); @@ -1587,11 +1618,20 @@ static int arm_smmu_add_device(struct device *dev) */ pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, &cfg->streamids[0]); - dev->archdata.iommu = cfg; + releasefn = __arm_smmu_release_pci_iommudata; } else { - dev->archdata.iommu = smmu; + struct arm_smmu_master *master; + + master = find_smmu_master(smmu, dev->of_node); + if (!master) { + ret = -ENODEV; + goto out_put_group; + } + + cfg = &master->cfg; } + iommu_group_set_iommudata(group, cfg, releasefn); ret = iommu_group_add_device(group, dev); out_put_group: @@ -1601,14 +1641,11 @@ out_put_group: static void arm_smmu_remove_device(struct device *dev) { - if (dev_is_pci(dev)) - kfree(dev->archdata.iommu); - - dev->archdata.iommu = NULL; iommu_group_remove_device(dev); } static const struct iommu_ops arm_smmu_ops = { + .capable = arm_smmu_capable, .domain_init = arm_smmu_domain_init, .domain_destroy = arm_smmu_domain_destroy, .attach_dev = arm_smmu_attach_dev, @@ -1616,7 +1653,6 @@ static const struct iommu_ops arm_smmu_ops = { .map = arm_smmu_map, .unmap = arm_smmu_unmap, .iova_to_phys = arm_smmu_iova_to_phys, - .domain_has_cap = arm_smmu_domain_has_cap, .add_device = arm_smmu_add_device, .remove_device = arm_smmu_remove_device, .pgsize_bitmap = (SECTION_SIZE | @@ -1702,10 +1738,6 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) u32 id; dev_notice(smmu->dev, "probing hardware configuration...\n"); - - /* Primecell ID */ - id = readl_relaxed(gr0_base + ARM_SMMU_GR0_PIDR2); - smmu->version = ((id >> PIDR2_ARCH_SHIFT) & PIDR2_ARCH_MASK) + 1; dev_notice(smmu->dev, "SMMUv%d with:\n", smmu->version); /* ID0 */ @@ -1716,6 +1748,13 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) return -ENODEV; } #endif + + /* Restrict available stages based on module parameter */ + if (force_stage == 1) + id &= ~(ID0_S2TS | ID0_NTS); + else if (force_stage == 2) + id &= ~(ID0_S1TS | ID0_NTS); + if (id & ID0_S1TS) { smmu->features |= ARM_SMMU_FEAT_TRANS_S1; dev_notice(smmu->dev, "\tstage 1 translation\n"); @@ -1732,8 +1771,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) } if (!(smmu->features & - (ARM_SMMU_FEAT_TRANS_S1 | ARM_SMMU_FEAT_TRANS_S2 | - ARM_SMMU_FEAT_TRANS_NESTED))) { + (ARM_SMMU_FEAT_TRANS_S1 | ARM_SMMU_FEAT_TRANS_S2))) { dev_err(smmu->dev, "\tno translation support!\n"); return -ENODEV; } @@ -1779,12 +1817,12 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) /* ID1 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID1); - smmu->pagesize = (id & ID1_PAGESIZE) ? SZ_64K : SZ_4K; + smmu->pgshift = (id & ID1_PAGESIZE) ? 16 : 12; /* Check for size mismatch of SMMU address space from mapped region */ size = 1 << (((id >> ID1_NUMPAGENDXB_SHIFT) & ID1_NUMPAGENDXB_MASK) + 1); - size *= (smmu->pagesize << 1); + size *= 2 << smmu->pgshift; if (smmu->size != size) dev_warn(smmu->dev, "SMMU address space size (0x%lx) differs from mapped region size (0x%lx)!\n", @@ -1803,28 +1841,21 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) /* ID2 */ id = readl_relaxed(gr0_base + ARM_SMMU_GR0_ID2); size = arm_smmu_id_size_to_bits((id >> ID2_IAS_SHIFT) & ID2_IAS_MASK); + smmu->s1_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); - /* - * Stage-1 output limited by stage-2 input size due to pgd - * allocation (PTRS_PER_PGD). - */ - if (smmu->features & ARM_SMMU_FEAT_TRANS_NESTED) { + /* Stage-2 input size limited due to pgd allocation (PTRS_PER_PGD) */ #ifdef CONFIG_64BIT - smmu->s1_output_size = min_t(unsigned long, VA_BITS, size); + smmu->s2_input_size = min_t(unsigned long, VA_BITS, size); #else - smmu->s1_output_size = min(32UL, size); + smmu->s2_input_size = min(32UL, size); #endif - } else { - smmu->s1_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, - size); - } /* The stage-2 output mask is also applied for bypass */ size = arm_smmu_id_size_to_bits((id >> ID2_OAS_SHIFT) & ID2_OAS_MASK); smmu->s2_output_size = min_t(unsigned long, PHYS_MASK_SHIFT, size); - if (smmu->version == 1) { - smmu->input_size = 32; + if (smmu->version == ARM_SMMU_V1) { + smmu->s1_input_size = 32; } else { #ifdef CONFIG_64BIT size = (id >> ID2_UBS_SHIFT) & ID2_UBS_MASK; @@ -1832,7 +1863,7 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) #else size = 32; #endif - smmu->input_size = size; + smmu->s1_input_size = size; if ((PAGE_SIZE == SZ_4K && !(id & ID2_PTFS_4K)) || (PAGE_SIZE == SZ_64K && !(id & ID2_PTFS_64K)) || @@ -1843,15 +1874,30 @@ static int arm_smmu_device_cfg_probe(struct arm_smmu_device *smmu) } } - dev_notice(smmu->dev, - "\t%lu-bit VA, %lu-bit IPA, %lu-bit PA\n", - smmu->input_size, smmu->s1_output_size, - smmu->s2_output_size); + if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) + dev_notice(smmu->dev, "\tStage-1: %lu-bit VA -> %lu-bit IPA\n", + smmu->s1_input_size, smmu->s1_output_size); + + if (smmu->features & ARM_SMMU_FEAT_TRANS_S2) + dev_notice(smmu->dev, "\tStage-2: %lu-bit IPA -> %lu-bit PA\n", + smmu->s2_input_size, smmu->s2_output_size); + return 0; } +static const struct of_device_id arm_smmu_of_match[] = { + { .compatible = "arm,smmu-v1", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,smmu-v2", .data = (void *)ARM_SMMU_V2 }, + { .compatible = "arm,mmu-400", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,mmu-401", .data = (void *)ARM_SMMU_V1 }, + { .compatible = "arm,mmu-500", .data = (void *)ARM_SMMU_V2 }, + { }, +}; +MODULE_DEVICE_TABLE(of, arm_smmu_of_match); + static int arm_smmu_device_dt_probe(struct platform_device *pdev) { + const struct of_device_id *of_id; struct resource *res; struct arm_smmu_device *smmu; struct device *dev = &pdev->dev; @@ -1866,6 +1912,9 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) } smmu->dev = dev; + of_id = of_match_node(arm_smmu_of_match, dev->of_node); + smmu->version = (enum arm_smmu_arch_version)of_id->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); smmu->base = devm_ioremap_resource(dev, res); if (IS_ERR(smmu->base)) @@ -1930,7 +1979,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev) parse_driver_options(smmu); - if (smmu->version > 1 && + if (smmu->version > ARM_SMMU_V1 && smmu->num_context_banks != smmu->num_context_irqs) { dev_err(dev, "found only %d context interrupt(s) but %d required\n", @@ -2011,17 +2060,6 @@ static int arm_smmu_device_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_OF -static struct of_device_id arm_smmu_of_match[] = { - { .compatible = "arm,smmu-v1", }, - { .compatible = "arm,smmu-v2", }, - { .compatible = "arm,mmu-400", }, - { .compatible = "arm,mmu-500", }, - { }, -}; -MODULE_DEVICE_TABLE(of, arm_smmu_of_match); -#endif - static struct platform_driver arm_smmu_driver = { .driver = { .owner = THIS_MODULE, diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c index 06d268abe95..c5c61cabd6e 100644 --- a/drivers/iommu/dmar.c +++ b/drivers/iommu/dmar.c @@ -155,6 +155,7 @@ dmar_alloc_pci_notify_info(struct pci_dev *dev, unsigned long event) if (event == BUS_NOTIFY_ADD_DEVICE) { for (tmp = dev; tmp; tmp = tmp->bus->self) { level--; + info->path[level].bus = tmp->bus->number; info->path[level].device = PCI_SLOT(tmp->devfn); info->path[level].function = PCI_FUNC(tmp->devfn); if (pci_is_root_bus(tmp->bus)) @@ -177,17 +178,33 @@ static bool dmar_match_pci_path(struct dmar_pci_notify_info *info, int bus, int i; if (info->bus != bus) - return false; + goto fallback; if (info->level != count) - return false; + goto fallback; for (i = 0; i < count; i++) { if (path[i].device != info->path[i].device || path[i].function != info->path[i].function) - return false; + goto fallback; } return true; + +fallback: + + if (count != 1) + return false; + + i = info->level - 1; + if (bus == info->path[i].bus && + path[0].device == info->path[i].device && + path[0].function == info->path[i].function) { + pr_info(FW_BUG "RMRR entry for device %02x:%02x.%x is broken - applying workaround\n", + bus, path[0].device, path[0].function); + return true; + } + + return false; } /* Return: > 0 if match found, 0 if no match found, < 0 if error happens */ @@ -247,7 +264,7 @@ int dmar_remove_dev_scope(struct dmar_pci_notify_info *info, u16 segment, for_each_active_dev_scope(devices, count, index, tmp) if (tmp == &info->dev->dev) { - rcu_assign_pointer(devices[index].dev, NULL); + RCU_INIT_POINTER(devices[index].dev, NULL); synchronize_rcu(); put_device(tmp); return 1; diff --git a/drivers/iommu/exynos-iommu.c b/drivers/iommu/exynos-iommu.c index d037e87a1fe..74233186f6f 100644 --- a/drivers/iommu/exynos-iommu.c +++ b/drivers/iommu/exynos-iommu.c @@ -32,7 +32,7 @@ typedef u32 sysmmu_iova_t; typedef u32 sysmmu_pte_t; -/* We does not consider super section mapping (16MB) */ +/* We do not consider super section mapping (16MB) */ #define SECT_ORDER 20 #define LPAGE_ORDER 16 #define SPAGE_ORDER 12 @@ -307,7 +307,7 @@ static void show_fault_information(const char *name, static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id) { - /* SYSMMU is in blocked when interrupt occurred. */ + /* SYSMMU is in blocked state when interrupt occurred. */ struct sysmmu_drvdata *data = dev_id; enum exynos_sysmmu_inttype itype; sysmmu_iova_t addr = -1; @@ -567,8 +567,8 @@ static void sysmmu_tlb_invalidate_entry(struct device *dev, sysmmu_iova_t iova, /* * L2TLB invalidation required * 4KB page: 1 invalidation - * 64KB page: 16 invalidation - * 1MB page: 64 invalidation + * 64KB page: 16 invalidations + * 1MB page: 64 invalidations * because it is set-associative TLB * with 8-way and 64 sets. * 1MB page can be cached in one of all sets. @@ -714,7 +714,7 @@ static int exynos_iommu_domain_init(struct iommu_domain *domain) if (!priv->lv2entcnt) goto err_counter; - /* w/a of System MMU v3.3 to prevent caching 1MiB mapping */ + /* Workaround for System MMU v3.3 to prevent caching 1MiB mapping */ for (i = 0; i < NUM_LV1ENTRIES; i += 8) { priv->pgtable[i + 0] = ZERO_LV2LINK; priv->pgtable[i + 1] = ZERO_LV2LINK; @@ -861,14 +861,14 @@ static sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *priv, pgtable_flush(sent, sent + 1); /* - * If pretched SLPD is a fault SLPD in zero_l2_table, FLPD cache - * may caches the address of zero_l2_table. This function - * replaces the zero_l2_table with new L2 page table to write - * valid mappings. + * If pre-fetched SLPD is a faulty SLPD in zero_l2_table, + * FLPD cache may cache the address of zero_l2_table. This + * function replaces the zero_l2_table with new L2 page table + * to write valid mappings. * Accessing the valid area may cause page fault since FLPD - * cache may still caches zero_l2_table for the valid area - * instead of new L2 page table that have the mapping - * information of the valid area + * cache may still cache zero_l2_table for the valid area + * instead of new L2 page table that has the mapping + * information of the valid area. * Thus any replacement of zero_l2_table with other valid L2 * page table must involve FLPD cache invalidation for System * MMU v3.3. @@ -963,27 +963,27 @@ static int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size, /* * *CAUTION* to the I/O virtual memory managers that support exynos-iommu: * - * System MMU v3.x have an advanced logic to improve address translation + * System MMU v3.x has advanced logic to improve address translation * performance with caching more page table entries by a page table walk. - * However, the logic has a bug that caching fault page table entries and System - * MMU reports page fault if the cached fault entry is hit even though the fault - * entry is updated to a valid entry after the entry is cached. - * To prevent caching fault page table entries which may be updated to valid - * entries later, the virtual memory manager should care about the w/a about the - * problem. The followings describe w/a. + * However, the logic has a bug that while caching faulty page table entries, + * System MMU reports page fault if the cached fault entry is hit even though + * the fault entry is updated to a valid entry after the entry is cached. + * To prevent caching faulty page table entries which may be updated to valid + * entries later, the virtual memory manager should care about the workaround + * for the problem. The following describes the workaround. * * Any two consecutive I/O virtual address regions must have a hole of 128KiB - * in maximum to prevent misbehavior of System MMU 3.x. (w/a of h/w bug) + * at maximum to prevent misbehavior of System MMU 3.x (workaround for h/w bug). * - * Precisely, any start address of I/O virtual region must be aligned by + * Precisely, any start address of I/O virtual region must be aligned with * the following sizes for System MMU v3.1 and v3.2. * System MMU v3.1: 128KiB * System MMU v3.2: 256KiB * * Because System MMU v3.3 caches page table entries more aggressively, it needs - * more w/a. - * - Any two consecutive I/O virtual regions must be have a hole of larger size - * than or equal size to 128KiB. + * more workarounds. + * - Any two consecutive I/O virtual regions must have a hole of size larger + * than or equal to 128KiB. * - Start address of an I/O virtual region must be aligned by 128KiB. */ static int exynos_iommu_map(struct iommu_domain *domain, unsigned long l_iova, @@ -1061,7 +1061,8 @@ static size_t exynos_iommu_unmap(struct iommu_domain *domain, goto err; } - *ent = ZERO_LV2LINK; /* w/a for h/w bug in Sysmem MMU v3.3 */ + /* workaround for h/w bug in System MMU v3.3 */ + *ent = ZERO_LV2LINK; pgtable_flush(ent, ent + 1); size = SECT_SIZE; goto done; diff --git a/drivers/iommu/fsl_pamu_domain.c b/drivers/iommu/fsl_pamu_domain.c index 56feed7cec1..c828f80d48b 100644 --- a/drivers/iommu/fsl_pamu_domain.c +++ b/drivers/iommu/fsl_pamu_domain.c @@ -411,8 +411,7 @@ static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain, return get_phys_addr(dma_domain, iova); } -static int fsl_pamu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool fsl_pamu_capable(enum iommu_cap cap) { return cap == IOMMU_CAP_CACHE_COHERENCY; } @@ -1080,6 +1079,7 @@ static u32 fsl_pamu_get_windows(struct iommu_domain *domain) } static const struct iommu_ops fsl_pamu_ops = { + .capable = fsl_pamu_capable, .domain_init = fsl_pamu_domain_init, .domain_destroy = fsl_pamu_domain_destroy, .attach_dev = fsl_pamu_attach_device, @@ -1089,7 +1089,6 @@ static const struct iommu_ops fsl_pamu_ops = { .domain_get_windows = fsl_pamu_get_windows, .domain_set_windows = fsl_pamu_set_windows, .iova_to_phys = fsl_pamu_iova_to_phys, - .domain_has_cap = fsl_pamu_domain_has_cap, .domain_set_attr = fsl_pamu_set_domain_attr, .domain_get_attr = fsl_pamu_get_domain_attr, .add_device = fsl_pamu_add_device, diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index 5619f264862..a27d6cb1a79 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -3865,8 +3865,7 @@ static int device_notifier(struct notifier_block *nb, if (iommu_dummy(dev)) return 0; - if (action != BUS_NOTIFY_UNBOUND_DRIVER && - action != BUS_NOTIFY_DEL_DEVICE) + if (action != BUS_NOTIFY_REMOVED_DEVICE) return 0; /* @@ -4415,17 +4414,14 @@ static phys_addr_t intel_iommu_iova_to_phys(struct iommu_domain *domain, return phys; } -static int intel_iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool intel_iommu_capable(enum iommu_cap cap) { - struct dmar_domain *dmar_domain = domain->priv; - if (cap == IOMMU_CAP_CACHE_COHERENCY) - return dmar_domain->iommu_snooping; + return domain_update_iommu_snooping(NULL) == 1; if (cap == IOMMU_CAP_INTR_REMAP) - return irq_remapping_enabled; + return irq_remapping_enabled == 1; - return 0; + return false; } static int intel_iommu_add_device(struct device *dev) @@ -4464,6 +4460,7 @@ static void intel_iommu_remove_device(struct device *dev) } static const struct iommu_ops intel_iommu_ops = { + .capable = intel_iommu_capable, .domain_init = intel_iommu_domain_init, .domain_destroy = intel_iommu_domain_destroy, .attach_dev = intel_iommu_attach_device, @@ -4471,7 +4468,6 @@ static const struct iommu_ops intel_iommu_ops = { .map = intel_iommu_map, .unmap = intel_iommu_unmap, .iova_to_phys = intel_iommu_iova_to_phys, - .domain_has_cap = intel_iommu_domain_has_cap, .add_device = intel_iommu_add_device, .remove_device = intel_iommu_remove_device, .pgsize_bitmap = INTEL_IOMMU_PGSIZES, diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c index 0df41f6264f..7c80661b35c 100644 --- a/drivers/iommu/intel_irq_remapping.c +++ b/drivers/iommu/intel_irq_remapping.c @@ -438,8 +438,7 @@ static void iommu_set_irq_remapping(struct intel_iommu *iommu, int mode) (addr) | IR_X2APIC_MODE(mode) | INTR_REMAP_TABLE_REG_SIZE); /* Set interrupt-remapping table pointer */ - iommu->gcmd |= DMA_GCMD_SIRTP; - writel(iommu->gcmd, iommu->reg + DMAR_GCMD_REG); + writel(iommu->gcmd | DMA_GCMD_SIRTP, iommu->reg + DMAR_GCMD_REG); IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, readl, (sts & DMA_GSTS_IRTPS), sts); @@ -1139,7 +1138,7 @@ static int intel_msi_setup_irq(struct pci_dev *pdev, unsigned int irq, return ret; } -static int intel_setup_hpet_msi(unsigned int irq, unsigned int id) +static int intel_alloc_hpet_msi(unsigned int irq, unsigned int id) { int ret = -1; struct intel_iommu *iommu; @@ -1170,5 +1169,5 @@ struct irq_remap_ops intel_irq_remap_ops = { .compose_msi_msg = intel_compose_msi_msg, .msi_alloc_irq = intel_msi_alloc_irq, .msi_setup_irq = intel_msi_setup_irq, - .setup_hpet_msi = intel_setup_hpet_msi, + .alloc_hpet_msi = intel_alloc_hpet_msi, }; diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 0639b9274b1..ed8b04867b1 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -30,6 +30,7 @@ #include <linux/notifier.h> #include <linux/err.h> #include <linux/pci.h> +#include <linux/bitops.h> #include <trace/events/iommu.h> static struct kset *iommu_group_kset; @@ -519,6 +520,9 @@ int iommu_group_id(struct iommu_group *group) } EXPORT_SYMBOL_GPL(iommu_group_id); +static struct iommu_group *get_pci_alias_group(struct pci_dev *pdev, + unsigned long *devfns); + /* * To consider a PCI device isolated, we require ACS to support Source * Validation, Request Redirection, Completer Redirection, and Upstream @@ -529,6 +533,86 @@ EXPORT_SYMBOL_GPL(iommu_group_id); */ #define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF) +/* + * For multifunction devices which are not isolated from each other, find + * all the other non-isolated functions and look for existing groups. For + * each function, we also need to look for aliases to or from other devices + * that may already have a group. + */ +static struct iommu_group *get_pci_function_alias_group(struct pci_dev *pdev, + unsigned long *devfns) +{ + struct pci_dev *tmp = NULL; + struct iommu_group *group; + + if (!pdev->multifunction || pci_acs_enabled(pdev, REQ_ACS_FLAGS)) + return NULL; + + for_each_pci_dev(tmp) { + if (tmp == pdev || tmp->bus != pdev->bus || + PCI_SLOT(tmp->devfn) != PCI_SLOT(pdev->devfn) || + pci_acs_enabled(tmp, REQ_ACS_FLAGS)) + continue; + + group = get_pci_alias_group(tmp, devfns); + if (group) { + pci_dev_put(tmp); + return group; + } + } + + return NULL; +} + +/* + * Look for aliases to or from the given device for exisiting groups. The + * dma_alias_devfn only supports aliases on the same bus, therefore the search + * space is quite small (especially since we're really only looking at pcie + * device, and therefore only expect multiple slots on the root complex or + * downstream switch ports). It's conceivable though that a pair of + * multifunction devices could have aliases between them that would cause a + * loop. To prevent this, we use a bitmap to track where we've been. + */ +static struct iommu_group *get_pci_alias_group(struct pci_dev *pdev, + unsigned long *devfns) +{ + struct pci_dev *tmp = NULL; + struct iommu_group *group; + + if (test_and_set_bit(pdev->devfn & 0xff, devfns)) + return NULL; + + group = iommu_group_get(&pdev->dev); + if (group) + return group; + + for_each_pci_dev(tmp) { + if (tmp == pdev || tmp->bus != pdev->bus) + continue; + + /* We alias them or they alias us */ + if (((pdev->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) && + pdev->dma_alias_devfn == tmp->devfn) || + ((tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN) && + tmp->dma_alias_devfn == pdev->devfn)) { + + group = get_pci_alias_group(tmp, devfns); + if (group) { + pci_dev_put(tmp); + return group; + } + + group = get_pci_function_alias_group(tmp, devfns); + if (group) { + pci_dev_put(tmp); + return group; + } + } + } + + return NULL; +} + struct group_for_pci_data { struct pci_dev *pdev; struct iommu_group *group; @@ -557,7 +641,7 @@ static struct iommu_group *iommu_group_get_for_pci_dev(struct pci_dev *pdev) struct group_for_pci_data data; struct pci_bus *bus; struct iommu_group *group = NULL; - struct pci_dev *tmp; + u64 devfns[4] = { 0 }; /* * Find the upstream DMA alias for the device. A device must not @@ -591,76 +675,21 @@ static struct iommu_group *iommu_group_get_for_pci_dev(struct pci_dev *pdev) } /* - * Next we need to consider DMA alias quirks. If one device aliases - * to another, they should be grouped together. It's theoretically - * possible that aliases could create chains of devices where each - * device aliases another device. If we then factor in multifunction - * ACS grouping requirements, each alias could incorporate a new slot - * with multiple functions, each with aliases. This is all extremely - * unlikely as DMA alias quirks are typically only used for PCIe - * devices where we usually have a single slot per bus. Furthermore, - * the alias quirk is usually to another function within the slot - * (and ACS multifunction is not supported) or to a different slot - * that doesn't physically exist. The likely scenario is therefore - * that everything on the bus gets grouped together. To reduce the - * problem space, share the IOMMU group for all devices on the bus - * if a DMA alias quirk is present on the bus. + * Look for existing groups on device aliases. If we alias another + * device or another device aliases us, use the same group. */ - tmp = NULL; - for_each_pci_dev(tmp) { - if (tmp->bus != pdev->bus || - !(tmp->dev_flags & PCI_DEV_FLAGS_DMA_ALIAS_DEVFN)) - continue; - - pci_dev_put(tmp); - tmp = NULL; - - /* We have an alias quirk, search for an existing group */ - for_each_pci_dev(tmp) { - struct iommu_group *group_tmp; - - if (tmp->bus != pdev->bus) - continue; - - group_tmp = iommu_group_get(&tmp->dev); - if (!group) { - group = group_tmp; - continue; - } - - if (group_tmp) { - WARN_ON(group != group_tmp); - iommu_group_put(group_tmp); - } - } - - return group ? group : iommu_group_alloc(); - } - - /* - * Non-multifunction devices or multifunction devices supporting - * ACS get their own group. - */ - if (!pdev->multifunction || pci_acs_enabled(pdev, REQ_ACS_FLAGS)) - return iommu_group_alloc(); + group = get_pci_alias_group(pdev, (unsigned long *)devfns); + if (group) + return group; /* - * Multifunction devices not supporting ACS share a group with other - * similar devices in the same slot. + * Look for existing groups on non-isolated functions on the same + * slot and aliases of those funcions, if any. No need to clear + * the search bitmap, the tested devfns are still valid. */ - tmp = NULL; - for_each_pci_dev(tmp) { - if (tmp == pdev || tmp->bus != pdev->bus || - PCI_SLOT(tmp->devfn) != PCI_SLOT(pdev->devfn) || - pci_acs_enabled(tmp, REQ_ACS_FLAGS)) - continue; - - group = iommu_group_get(&tmp->dev); - if (group) { - pci_dev_put(tmp); - return group; - } - } + group = get_pci_function_alias_group(pdev, (unsigned long *)devfns); + if (group) + return group; /* No shared group found, allocate new */ return iommu_group_alloc(); @@ -770,18 +799,26 @@ static int iommu_bus_notifier(struct notifier_block *nb, return 0; } -static struct notifier_block iommu_bus_nb = { - .notifier_call = iommu_bus_notifier, -}; - -static void iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops) +static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops) { + int err; + struct notifier_block *nb; struct iommu_callback_data cb = { .ops = ops, }; - bus_register_notifier(bus, &iommu_bus_nb); - bus_for_each_dev(bus, NULL, &cb, add_iommu_group); + nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL); + if (!nb) + return -ENOMEM; + + nb->notifier_call = iommu_bus_notifier; + + err = bus_register_notifier(bus, nb); + if (err) { + kfree(nb); + return err; + } + return bus_for_each_dev(bus, NULL, &cb, add_iommu_group); } /** @@ -805,9 +842,7 @@ int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops) bus->iommu_ops = ops; /* Do IOMMU specific setup for this bus-type */ - iommu_bus_init(bus, ops); - - return 0; + return iommu_bus_init(bus, ops); } EXPORT_SYMBOL_GPL(bus_set_iommu); @@ -817,6 +852,15 @@ bool iommu_present(struct bus_type *bus) } EXPORT_SYMBOL_GPL(iommu_present); +bool iommu_capable(struct bus_type *bus, enum iommu_cap cap) +{ + if (!bus->iommu_ops || !bus->iommu_ops->capable) + return false; + + return bus->iommu_ops->capable(cap); +} +EXPORT_SYMBOL_GPL(iommu_capable); + /** * iommu_set_fault_handler() - set a fault handler for an iommu domain * @domain: iommu domain @@ -947,16 +991,6 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) } EXPORT_SYMBOL_GPL(iommu_iova_to_phys); -int iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) -{ - if (unlikely(domain->ops->domain_has_cap == NULL)) - return 0; - - return domain->ops->domain_has_cap(domain, cap); -} -EXPORT_SYMBOL_GPL(iommu_domain_has_cap); - static size_t iommu_pgsize(struct iommu_domain *domain, unsigned long addr_merge, size_t size) { diff --git a/drivers/iommu/irq_remapping.c b/drivers/iommu/irq_remapping.c index 33c43952408..74a1767c89b 100644 --- a/drivers/iommu/irq_remapping.c +++ b/drivers/iommu/irq_remapping.c @@ -12,6 +12,7 @@ #include <asm/processor.h> #include <asm/x86_init.h> #include <asm/apic.h> +#include <asm/hpet.h> #include "irq_remapping.h" @@ -345,10 +346,16 @@ static int msi_setup_remapped_irq(struct pci_dev *pdev, unsigned int irq, int setup_hpet_msi_remapped(unsigned int irq, unsigned int id) { - if (!remap_ops || !remap_ops->setup_hpet_msi) + int ret; + + if (!remap_ops || !remap_ops->alloc_hpet_msi) return -ENODEV; - return remap_ops->setup_hpet_msi(irq, id); + ret = remap_ops->alloc_hpet_msi(irq, id); + if (ret) + return -EINVAL; + + return default_setup_hpet_msi(irq, id); } void panic_if_irq_remap(const char *msg) diff --git a/drivers/iommu/irq_remapping.h b/drivers/iommu/irq_remapping.h index 90c4dae5a46..fde250f86e6 100644 --- a/drivers/iommu/irq_remapping.h +++ b/drivers/iommu/irq_remapping.h @@ -80,7 +80,7 @@ struct irq_remap_ops { int (*msi_setup_irq)(struct pci_dev *, unsigned int, int, int); /* Setup interrupt remapping for an HPET MSI */ - int (*setup_hpet_msi)(unsigned int, unsigned int); + int (*alloc_hpet_msi)(unsigned int, unsigned int); }; extern struct irq_remap_ops intel_irq_remap_ops; diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c index 49f41d6e02f..6e3dcc289d5 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -603,10 +603,9 @@ fail: return ret; } -static int msm_iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool msm_iommu_capable(enum iommu_cap cap) { - return 0; + return false; } static void print_ctx_regs(void __iomem *base, int ctx) @@ -675,6 +674,7 @@ fail: } static const struct iommu_ops msm_iommu_ops = { + .capable = msm_iommu_capable, .domain_init = msm_iommu_domain_init, .domain_destroy = msm_iommu_domain_destroy, .attach_dev = msm_iommu_attach_dev, @@ -682,7 +682,6 @@ static const struct iommu_ops msm_iommu_ops = { .map = msm_iommu_map, .unmap = msm_iommu_unmap, .iova_to_phys = msm_iommu_iova_to_phys, - .domain_has_cap = msm_iommu_domain_has_cap, .pgsize_bitmap = MSM_IOMMU_PGSIZES, }; diff --git a/drivers/iommu/omap-iommu.c b/drivers/iommu/omap-iommu.c index e202b0c2412..36278870e84 100644 --- a/drivers/iommu/omap-iommu.c +++ b/drivers/iommu/omap-iommu.c @@ -26,6 +26,7 @@ #include <linux/of.h> #include <linux/of_iommu.h> #include <linux/of_irq.h> +#include <linux/of_platform.h> #include <asm/cacheflush.h> @@ -892,19 +893,11 @@ static struct omap_iommu *omap_iommu_attach(const char *name, u32 *iopgd) goto err_enable; flush_iotlb_all(obj); - if (!try_module_get(obj->owner)) { - err = -ENODEV; - goto err_module; - } - spin_unlock(&obj->iommu_lock); dev_dbg(obj->dev, "%s: %s\n", __func__, obj->name); return obj; -err_module: - if (obj->refcount == 1) - iommu_disable(obj); err_enable: obj->refcount--; spin_unlock(&obj->iommu_lock); @@ -925,8 +918,6 @@ static void omap_iommu_detach(struct omap_iommu *obj) if (--obj->refcount == 0) iommu_disable(obj); - module_put(obj->owner); - obj->iopgd = NULL; spin_unlock(&obj->iommu_lock); @@ -1006,7 +997,7 @@ static int omap_iommu_remove(struct platform_device *pdev) return 0; } -static struct of_device_id omap_iommu_of_match[] = { +static const struct of_device_id omap_iommu_of_match[] = { { .compatible = "ti,omap2-iommu" }, { .compatible = "ti,omap4-iommu" }, { .compatible = "ti,dra7-iommu" }, @@ -1091,6 +1082,11 @@ omap_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) struct omap_iommu_arch_data *arch_data = dev->archdata.iommu; int ret = 0; + if (!arch_data || !arch_data->name) { + dev_err(dev, "device doesn't have an associated iommu\n"); + return -EINVAL; + } + spin_lock(&omap_domain->lock); /* only a single device is supported per domain for now */ @@ -1239,6 +1235,7 @@ static int omap_iommu_add_device(struct device *dev) { struct omap_iommu_arch_data *arch_data; struct device_node *np; + struct platform_device *pdev; /* * Allocate the archdata iommu structure for DT-based devices. @@ -1253,13 +1250,19 @@ static int omap_iommu_add_device(struct device *dev) if (!np) return 0; + pdev = of_find_device_by_node(np); + if (WARN_ON(!pdev)) { + of_node_put(np); + return -EINVAL; + } + arch_data = kzalloc(sizeof(*arch_data), GFP_KERNEL); if (!arch_data) { of_node_put(np); return -ENOMEM; } - arch_data->name = kstrdup(dev_name(dev), GFP_KERNEL); + arch_data->name = kstrdup(dev_name(&pdev->dev), GFP_KERNEL); dev->archdata.iommu = arch_data; of_node_put(np); diff --git a/drivers/iommu/omap-iommu.h b/drivers/iommu/omap-iommu.h index 1275a822934..4f1b68c08c1 100644 --- a/drivers/iommu/omap-iommu.h +++ b/drivers/iommu/omap-iommu.h @@ -28,7 +28,6 @@ struct iotlb_entry { struct omap_iommu { const char *name; - struct module *owner; void __iomem *regbase; struct device *dev; void *isr_priv; diff --git a/drivers/iommu/tegra-gart.c b/drivers/iommu/tegra-gart.c index b10a8ecede8..a6d76abf2c0 100644 --- a/drivers/iommu/tegra-gart.c +++ b/drivers/iommu/tegra-gart.c @@ -303,13 +303,13 @@ static phys_addr_t gart_iommu_iova_to_phys(struct iommu_domain *domain, return pa; } -static int gart_iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool gart_iommu_capable(enum iommu_cap cap) { - return 0; + return false; } static const struct iommu_ops gart_iommu_ops = { + .capable = gart_iommu_capable, .domain_init = gart_iommu_domain_init, .domain_destroy = gart_iommu_domain_destroy, .attach_dev = gart_iommu_attach_dev, @@ -317,7 +317,6 @@ static const struct iommu_ops gart_iommu_ops = { .map = gart_iommu_map, .unmap = gart_iommu_unmap, .iova_to_phys = gart_iommu_iova_to_phys, - .domain_has_cap = gart_iommu_domain_has_cap, .pgsize_bitmap = GART_IOMMU_PGSIZES, }; @@ -416,7 +415,7 @@ static const struct dev_pm_ops tegra_gart_pm_ops = { .resume = tegra_gart_resume, }; -static struct of_device_id tegra_gart_of_match[] = { +static const struct of_device_id tegra_gart_of_match[] = { { .compatible = "nvidia,tegra20-gart", }, { }, }; diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c index 3ded3894623..3afdf43f732 100644 --- a/drivers/iommu/tegra-smmu.c +++ b/drivers/iommu/tegra-smmu.c @@ -780,10 +780,9 @@ static phys_addr_t smmu_iommu_iova_to_phys(struct iommu_domain *domain, return PFN_PHYS(pfn); } -static int smmu_iommu_domain_has_cap(struct iommu_domain *domain, - unsigned long cap) +static bool smmu_iommu_capable(enum iommu_cap cap) { - return 0; + return false; } static int smmu_iommu_attach_dev(struct iommu_domain *domain, @@ -949,6 +948,7 @@ static void smmu_iommu_domain_destroy(struct iommu_domain *domain) } static const struct iommu_ops smmu_iommu_ops = { + .capable = smmu_iommu_capable, .domain_init = smmu_iommu_domain_init, .domain_destroy = smmu_iommu_domain_destroy, .attach_dev = smmu_iommu_attach_dev, @@ -956,7 +956,6 @@ static const struct iommu_ops smmu_iommu_ops = { .map = smmu_iommu_map, .unmap = smmu_iommu_unmap, .iova_to_phys = smmu_iommu_iova_to_phys, - .domain_has_cap = smmu_iommu_domain_has_cap, .pgsize_bitmap = SMMU_IOMMU_PGSIZES, }; @@ -1260,7 +1259,7 @@ static const struct dev_pm_ops tegra_smmu_pm_ops = { .resume = tegra_smmu_resume, }; -static struct of_device_id tegra_smmu_of_match[] = { +static const struct of_device_id tegra_smmu_of_match[] = { { .compatible = "nvidia,tegra30-smmu", }, { }, }; diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c index a93b4d0134a..84e31310723 100644 --- a/drivers/mfd/88pm860x-i2c.c +++ b/drivers/mfd/88pm860x-i2c.c @@ -122,7 +122,7 @@ static int read_device(struct i2c_client *i2c, int reg, static int write_device(struct i2c_client *i2c, int reg, int bytes, void *src) { - unsigned char buf[bytes + 1]; + unsigned char buf[2]; struct i2c_adapter *adap = i2c->adapter; struct i2c_msg msg; int ret; @@ -140,26 +140,6 @@ static int write_device(struct i2c_client *i2c, int reg, return 0; } -int pm860x_page_reg_read(struct i2c_client *i2c, int reg) -{ - unsigned char zero = 0; - unsigned char data; - int ret; - - i2c_lock_adapter(i2c->adapter); - read_device(i2c, 0xFA, 0, &zero); - read_device(i2c, 0xFB, 0, &zero); - read_device(i2c, 0xFF, 0, &zero); - ret = read_device(i2c, reg, 1, &data); - if (ret >= 0) - ret = (int)data; - read_device(i2c, 0xFE, 0, &zero); - read_device(i2c, 0xFC, 0, &zero); - i2c_unlock_adapter(i2c->adapter); - return ret; -} -EXPORT_SYMBOL(pm860x_page_reg_read); - int pm860x_page_reg_write(struct i2c_client *i2c, int reg, unsigned char data) { @@ -195,47 +175,3 @@ int pm860x_page_bulk_read(struct i2c_client *i2c, int reg, return ret; } EXPORT_SYMBOL(pm860x_page_bulk_read); - -int pm860x_page_bulk_write(struct i2c_client *i2c, int reg, - int count, unsigned char *buf) -{ - unsigned char zero = 0; - int ret; - - i2c_lock_adapter(i2c->adapter); - read_device(i2c, 0xFA, 0, &zero); - read_device(i2c, 0xFB, 0, &zero); - read_device(i2c, 0xFF, 0, &zero); - ret = write_device(i2c, reg, count, buf); - read_device(i2c, 0xFE, 0, &zero); - read_device(i2c, 0xFC, 0, &zero); - i2c_unlock_adapter(i2c->adapter); - i2c_unlock_adapter(i2c->adapter); - return ret; -} -EXPORT_SYMBOL(pm860x_page_bulk_write); - -int pm860x_page_set_bits(struct i2c_client *i2c, int reg, - unsigned char mask, unsigned char data) -{ - unsigned char zero; - unsigned char value; - int ret; - - i2c_lock_adapter(i2c->adapter); - read_device(i2c, 0xFA, 0, &zero); - read_device(i2c, 0xFB, 0, &zero); - read_device(i2c, 0xFF, 0, &zero); - ret = read_device(i2c, reg, 1, &value); - if (ret < 0) - goto out; - value &= ~mask; - value |= data; - ret = write_device(i2c, reg, 1, &value); -out: - read_device(i2c, 0xFE, 0, &zero); - read_device(i2c, 0xFC, 0, &zero); - i2c_unlock_adapter(i2c->adapter); - return ret; -} -EXPORT_SYMBOL(pm860x_page_set_bits); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index cf66ef1ffaf..1456ea70bbc 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -210,6 +210,19 @@ config MFD_MC13XXX_I2C help Select this if your MC13xxx is connected via an I2C bus. +config MFD_HI6421_PMIC + tristate "HiSilicon Hi6421 PMU/Codec IC" + depends on OF + select MFD_CORE + select REGMAP_MMIO + help + Add support for HiSilicon Hi6421 PMIC. Hi6421 includes multi- + functions, such as regulators, RTC, codec, Coulomb counter, etc. + This driver includes core APIs _only_. You have to select + individul components like voltage regulators under corresponding + menus in order to enable them. + We communicate with the Hi6421 via memory-mapped I/O. + config HTC_EGPIO bool "HTC EGPIO support" depends on GPIOLIB && ARM @@ -554,6 +567,21 @@ config MFD_PM8921_CORE Say M here if you want to include support for PM8921 chip as a module. This will build a module called "pm8921-core". +config MFD_SPMI_PMIC + tristate "Qualcomm SPMI PMICs" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on SPMI + select REGMAP_SPMI + help + This enables support for the Qualcomm SPMI PMICs. + These PMICs are currently used with the Snapdragon 800 series of + SoCs. Note, that this will only be useful paired with descriptions + of the independent functions as children nodes in the device tree. + + Say M here if you want to include support for the SPMI PMIC + series as a module. The module will be called "qcom-spmi-pmic". + config MFD_RDC321X tristate "RDC R-321x southbridge" select MFD_CORE @@ -597,6 +625,30 @@ config MFD_RC5T583 Additional drivers must be enabled in order to use the different functionality of the device. +config MFD_RK808 + tristate "Rockchip RK808 Power Management chip" + depends on I2C && OF + select MFD_CORE + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the RK808 + Power Management chips. + This driver provides common support for accessing the device + through I2C interface. The device supports multiple sub-devices + including interrupts, RTC, LDO & DCDC regulators, and onkey. + +config MFD_RN5T618 + tristate "Ricoh RN5T5618 PMIC" + depends on I2C + select MFD_CORE + select REGMAP_I2C + help + Say yes here to add support for the Ricoh RN5T618 PMIC. This + driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_SEC_CORE bool "SAMSUNG Electronics PMIC Series Support" depends on I2C=y @@ -1243,11 +1295,11 @@ config MFD_WM8350_I2C selected to enable support for the functionality of the chip. config MFD_WM8994 - bool "Wolfson Microelectronics WM8994" + tristate "Wolfson Microelectronics WM8994" select MFD_CORE select REGMAP_I2C select REGMAP_IRQ - depends on I2C=y + depends on I2C help The WM8994 is a highly integrated hi-fi CODEC designed for smartphone applicatiosn. As well as audio functionality it diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index d58068aa8aa..8bd54b1253a 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -153,6 +153,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o +obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o @@ -160,6 +161,8 @@ obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o obj-$(CONFIG_MFD_PALMAS) += palmas.o obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o +obj-$(CONFIG_MFD_RK808) += rk808.o +obj-$(CONFIG_MFD_RN5T618) += rn5t618.o obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o obj-$(CONFIG_MFD_SYSCON) += syscon.o obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o @@ -170,6 +173,7 @@ obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o obj-$(CONFIG_MFD_MENF21BMC) += menf21bmc.o +obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o diff --git a/drivers/mfd/arizona-core.c b/drivers/mfd/arizona-core.c index 10a0cb90619..bce7c0784b6 100644 --- a/drivers/mfd/arizona-core.c +++ b/drivers/mfd/arizona-core.c @@ -393,18 +393,6 @@ static int arizona_runtime_resume(struct device *dev) break; } - switch (arizona->type) { - case WM5102: - ret = wm5102_patch(arizona); - if (ret != 0) { - dev_err(arizona->dev, "Failed to apply patch: %d\n", - ret); - goto err; - } - default: - break; - } - ret = regcache_sync(arizona->regmap); if (ret != 0) { dev_err(arizona->dev, "Failed to restore register cache\n"); @@ -534,7 +522,11 @@ EXPORT_SYMBOL_GPL(arizona_of_get_named_gpio); static int arizona_of_get_core_pdata(struct arizona *arizona) { struct arizona_pdata *pdata = &arizona->pdata; + struct property *prop; + const __be32 *cur; + u32 val; int ret, i; + int count = 0; pdata->reset = arizona_of_get_named_gpio(arizona, "wlf,reset", true); @@ -560,6 +552,15 @@ static int arizona_of_get_core_pdata(struct arizona *arizona) ret); } + of_property_for_each_u32(arizona->dev->of_node, "wlf,inmode", prop, + cur, val) { + if (count == ARRAY_SIZE(arizona->pdata.inmode)) + break; + + arizona->pdata.inmode[count] = val; + count++; + } + return 0; } @@ -784,7 +785,8 @@ int arizona_dev_init(struct arizona *arizona) /* Ensure device startup is complete */ switch (arizona->type) { case WM5102: - ret = regmap_read(arizona->regmap, 0x19, &val); + ret = regmap_read(arizona->regmap, + ARIZONA_WRITE_SEQUENCER_CTRL_3, &val); if (ret != 0) dev_err(dev, "Failed to check write sequencer state: %d\n", @@ -945,6 +947,7 @@ int arizona_dev_init(struct arizona *arizona) regmap_update_bits(arizona->regmap, ARIZONA_MIC_BIAS_CTRL_1 + i, ARIZONA_MICB1_LVL_MASK | + ARIZONA_MICB1_EXT_CAP | ARIZONA_MICB1_DISCH | ARIZONA_MICB1_BYPASS | ARIZONA_MICB1_RATE, val); diff --git a/drivers/mfd/arizona-irq.c b/drivers/mfd/arizona-irq.c index d420dbc0e2b..3a3fe7cc6d6 100644 --- a/drivers/mfd/arizona-irq.c +++ b/drivers/mfd/arizona-irq.c @@ -152,10 +152,18 @@ static void arizona_irq_disable(struct irq_data *data) { } +static int arizona_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct arizona *arizona = irq_data_get_irq_chip_data(data); + + return irq_set_irq_wake(arizona->irq, on); +} + static struct irq_chip arizona_irq_chip = { .name = "arizona", .irq_disable = arizona_irq_disable, .irq_enable = arizona_irq_enable, + .irq_set_wake = arizona_irq_set_wake, }; static int arizona_irq_map(struct irq_domain *h, unsigned int virq, @@ -164,7 +172,7 @@ static int arizona_irq_map(struct irq_domain *h, unsigned int virq, struct regmap_irq_chip_data *data = h->host_data; irq_set_chip_data(virq, data); - irq_set_chip_and_handler(virq, &arizona_irq_chip, handle_edge_irq); + irq_set_chip_and_handler(virq, &arizona_irq_chip, handle_simple_irq); irq_set_nested_thread(virq, 1); /* ARM needs us to explicitly flag the IRQ as valid @@ -282,7 +290,7 @@ int arizona_irq_init(struct arizona *arizona) ret = regmap_add_irq_chip(arizona->regmap, irq_create_mapping(arizona->virq, 0), - IRQF_ONESHOT, -1, aod, + IRQF_ONESHOT, 0, aod, &arizona->aod_irq_chip); if (ret != 0) { dev_err(arizona->dev, "Failed to add AOD IRQs: %d\n", ret); @@ -291,7 +299,7 @@ int arizona_irq_init(struct arizona *arizona) ret = regmap_add_irq_chip(arizona->regmap, irq_create_mapping(arizona->virq, 1), - IRQF_ONESHOT, -1, irq, + IRQF_ONESHOT, 0, irq, &arizona->irq_chip); if (ret != 0) { dev_err(arizona->dev, "Failed to add main IRQs: %d\n", ret); diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c index dee653989e3..6231adbb295 100644 --- a/drivers/mfd/axp20x.c +++ b/drivers/mfd/axp20x.c @@ -140,15 +140,6 @@ static const struct regmap_irq_chip axp20x_regmap_irq_chip = { .init_ack_masked = true, }; -static const char * const axp20x_supplies[] = { - "acin", - "vin2", - "vin3", - "ldo24in", - "ldo3in", - "ldo5in", -}; - static struct mfd_cell axp20x_cells[] = { { .name = "axp20x-pek", @@ -156,8 +147,6 @@ static struct mfd_cell axp20x_cells[] = { .resources = axp20x_pek_resources, }, { .name = "axp20x-regulator", - .parent_supplies = axp20x_supplies, - .num_parent_supplies = ARRAY_SIZE(axp20x_supplies), }, }; diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index 4873f9c5045..fc0c81ef04f 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -23,6 +23,9 @@ #include <linux/mfd/core.h> #include <linux/mfd/cros_ec.h> #include <linux/mfd/cros_ec_commands.h> +#include <linux/delay.h> + +#define EC_COMMAND_RETRIES 50 int cros_ec_prepare_tx(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) @@ -62,6 +65,49 @@ int cros_ec_check_result(struct cros_ec_device *ec_dev, } EXPORT_SYMBOL(cros_ec_check_result); +int cros_ec_cmd_xfer(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int ret; + + mutex_lock(&ec_dev->lock); + ret = ec_dev->cmd_xfer(ec_dev, msg); + if (msg->result == EC_RES_IN_PROGRESS) { + int i; + struct cros_ec_command status_msg; + struct ec_response_get_comms_status status; + + status_msg.version = 0; + status_msg.command = EC_CMD_GET_COMMS_STATUS; + status_msg.outdata = NULL; + status_msg.outsize = 0; + status_msg.indata = (uint8_t *)&status; + status_msg.insize = sizeof(status); + + /* + * Query the EC's status until it's no longer busy or + * we encounter an error. + */ + for (i = 0; i < EC_COMMAND_RETRIES; i++) { + usleep_range(10000, 11000); + + ret = ec_dev->cmd_xfer(ec_dev, &status_msg); + if (ret < 0) + break; + + msg->result = status_msg.result; + if (status_msg.result != EC_RES_SUCCESS) + break; + if (!(status.flags & EC_COMMS_STATUS_PROCESSING)) + break; + } + } + mutex_unlock(&ec_dev->lock); + + return ret; +} +EXPORT_SYMBOL(cros_ec_cmd_xfer); + static const struct mfd_cell cros_devs[] = { { .name = "cros-ec-keyb", @@ -91,6 +137,8 @@ int cros_ec_register(struct cros_ec_device *ec_dev) return -ENOMEM; } + mutex_init(&ec_dev->lock); + err = mfd_add_devices(dev, 0, cros_devs, ARRAY_SIZE(cros_devs), NULL, ec_dev->irq, NULL); diff --git a/drivers/mfd/cros_ec_spi.c b/drivers/mfd/cros_ec_spi.c index 588c700af39..bf6e08e8013 100644 --- a/drivers/mfd/cros_ec_spi.c +++ b/drivers/mfd/cros_ec_spi.c @@ -65,6 +65,12 @@ */ #define EC_SPI_RECOVERY_TIME_NS (200 * 1000) +/* + * The EC is unresponsive for a time after a reboot command. Add a + * simple delay to make sure that the bus stays locked. + */ +#define EC_REBOOT_DELAY_MS 50 + /** * struct cros_ec_spi - information about a SPI-connected EC * @@ -73,13 +79,11 @@ * if no record * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that * is sent when we want to turn off CS at the end of a transaction. - * @lock: mutex to ensure only one user of cros_ec_cmd_xfer_spi at a time */ struct cros_ec_spi { struct spi_device *spi; s64 last_transfer_ns; unsigned int end_of_msg_delay; - struct mutex lock; }; static void debug_packet(struct device *dev, const char *name, u8 *ptr, @@ -226,13 +230,6 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, int sum; int ret = 0, final_ret; - /* - * We have the shared ec_dev buffer plus we do lots of separate spi_sync - * calls, so we need to make sure only one person is using this at a - * time. - */ - mutex_lock(&ec_spi->lock); - len = cros_ec_prepare_tx(ec_dev, ec_msg); dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); @@ -318,7 +315,9 @@ static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, ret = len; exit: - mutex_unlock(&ec_spi->lock); + if (ec_msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + return ret; } @@ -350,7 +349,6 @@ static int cros_ec_spi_probe(struct spi_device *spi) if (ec_spi == NULL) return -ENOMEM; ec_spi->spi = spi; - mutex_init(&ec_spi->lock); ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); if (!ec_dev) return -ENOMEM; diff --git a/drivers/mfd/da9052-core.c b/drivers/mfd/da9052-core.c index e8af816d73a..52a0c2f6264 100644 --- a/drivers/mfd/da9052-core.c +++ b/drivers/mfd/da9052-core.c @@ -522,7 +522,7 @@ static const struct mfd_cell da9052_subdev_info[] = { }, }; -struct regmap_config da9052_regmap_config = { +const struct regmap_config da9052_regmap_config = { .reg_bits = 8, .val_bits = 8, diff --git a/drivers/mfd/da9052-i2c.c b/drivers/mfd/da9052-i2c.c index 6da8ec8ff80..ec39287a245 100644 --- a/drivers/mfd/da9052-i2c.c +++ b/drivers/mfd/da9052-i2c.c @@ -140,13 +140,6 @@ static int da9052_i2c_probe(struct i2c_client *client, if (!da9052) return -ENOMEM; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA)) { - dev_info(&client->dev, "Error in %s:i2c_check_functionality\n", - __func__); - return -ENODEV; - } - da9052->dev = &client->dev; da9052->chip_irq = client->irq; da9052->fix_io = da9052_i2c_fix; diff --git a/drivers/mfd/da9052-spi.c b/drivers/mfd/da9052-spi.c index 17666b40b70..45ae0b7d13e 100644 --- a/drivers/mfd/da9052-spi.c +++ b/drivers/mfd/da9052-spi.c @@ -23,6 +23,7 @@ static int da9052_spi_probe(struct spi_device *spi) { + struct regmap_config config; int ret; const struct spi_device_id *id = spi_get_device_id(spi); struct da9052 *da9052; @@ -40,10 +41,10 @@ static int da9052_spi_probe(struct spi_device *spi) spi_set_drvdata(spi, da9052); - da9052_regmap_config.read_flag_mask = 1; - da9052_regmap_config.write_flag_mask = 0; + config = da9052_regmap_config; + config.read_flag_mask = 1; - da9052->regmap = devm_regmap_init_spi(spi, &da9052_regmap_config); + da9052->regmap = devm_regmap_init_spi(spi, &config); if (IS_ERR(da9052->regmap)) { ret = PTR_ERR(da9052->regmap); dev_err(&spi->dev, "Failed to allocate register map: %d\n", diff --git a/drivers/mfd/hi6421-pmic-core.c b/drivers/mfd/hi6421-pmic-core.c new file mode 100644 index 00000000000..321a2656fd0 --- /dev/null +++ b/drivers/mfd/hi6421-pmic-core.c @@ -0,0 +1,113 @@ +/* + * Device driver for Hi6421 IC + * + * Copyright (c) <2011-2014> HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * Copyright (c) <2013-2014> Linaro Ltd. + * http://www.linaro.org + * + * Author: Guodong Xu <guodong.xu@linaro.org> + * + * 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 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 <linux/device.h> +#include <linux/err.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/hi6421-pmic.h> + +static const struct mfd_cell hi6421_devs[] = { + { .name = "hi6421-regulator", }, +}; + +static struct regmap_config hi6421_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 8, + .max_register = HI6421_REG_TO_BUS_ADDR(HI6421_REG_MAX), +}; + +static int hi6421_pmic_probe(struct platform_device *pdev) +{ + struct hi6421_pmic *pmic; + struct resource *res; + void __iomem *base; + int ret; + + pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + pmic->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, + &hi6421_regmap_config); + if (IS_ERR(pmic->regmap)) { + dev_err(&pdev->dev, + "regmap init failed: %ld\n", PTR_ERR(pmic->regmap)); + return PTR_ERR(pmic->regmap); + } + + /* set over-current protection debounce 8ms */ + regmap_update_bits(pmic->regmap, HI6421_OCP_DEB_CTRL_REG, + (HI6421_OCP_DEB_SEL_MASK + | HI6421_OCP_EN_DEBOUNCE_MASK + | HI6421_OCP_AUTO_STOP_MASK), + (HI6421_OCP_DEB_SEL_8MS + | HI6421_OCP_EN_DEBOUNCE_ENABLE)); + + platform_set_drvdata(pdev, pmic); + + ret = mfd_add_devices(&pdev->dev, 0, hi6421_devs, + ARRAY_SIZE(hi6421_devs), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "add mfd devices failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int hi6421_pmic_remove(struct platform_device *pdev) +{ + mfd_remove_devices(&pdev->dev); + + return 0; +} + +static struct of_device_id of_hi6421_pmic_match_tbl[] = { + { .compatible = "hisilicon,hi6421-pmic", }, + { }, +}; + +static struct platform_driver hi6421_pmic_driver = { + .driver = { + .name = "hi6421_pmic", + .of_match_table = of_hi6421_pmic_match_tbl, + }, + .probe = hi6421_pmic_probe, + .remove = hi6421_pmic_remove, +}; +module_platform_driver(hi6421_pmic_driver); + +MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>"); +MODULE_DESCRIPTION("Hi6421 PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/htc-i2cpld.c b/drivers/mfd/htc-i2cpld.c index adbbce0ff63..ebb9cf19e34 100644 --- a/drivers/mfd/htc-i2cpld.c +++ b/drivers/mfd/htc-i2cpld.c @@ -227,15 +227,12 @@ static irqreturn_t htcpld_handler(int irq, void *dev) static void htcpld_chip_set(struct gpio_chip *chip, unsigned offset, int val) { struct i2c_client *client; - struct htcpld_chip *chip_data; + struct htcpld_chip *chip_data = + container_of(chip, struct htcpld_chip, chip_out); unsigned long flags; - chip_data = container_of(chip, struct htcpld_chip, chip_out); - if (!chip_data) - return; - client = chip_data->client; - if (client == NULL) + if (!client) return; spin_lock_irqsave(&chip_data->lock, flags); @@ -261,31 +258,18 @@ static void htcpld_chip_set_ni(struct work_struct *work) static int htcpld_chip_get(struct gpio_chip *chip, unsigned offset) { struct htcpld_chip *chip_data; - int val = 0; - int is_input = 0; - - /* Try out first */ - chip_data = container_of(chip, struct htcpld_chip, chip_out); - if (!chip_data) { - /* Try in */ - is_input = 1; - chip_data = container_of(chip, struct htcpld_chip, chip_in); - if (!chip_data) - return -EINVAL; - } + u8 cache; - /* Determine if this is an input or output GPIO */ - if (!is_input) - /* Use the output cache */ - val = (chip_data->cache_out >> offset) & 1; - else - /* Use the input cache */ - val = (chip_data->cache_in >> offset) & 1; + if (!strncmp(chip->label, "htcpld-out", 10)) { + chip_data = container_of(chip, struct htcpld_chip, chip_out); + cache = chip_data->cache_out; + } else if (!strncmp(chip->label, "htcpld-in", 9)) { + chip_data = container_of(chip, struct htcpld_chip, chip_in); + cache = chip_data->cache_in; + } else + return -EINVAL; - if (val) - return 1; - else - return 0; + return (cache >> offset) & 1; } static int htcpld_direction_output(struct gpio_chip *chip, @@ -376,7 +360,7 @@ static int htcpld_register_chip_i2c( plat_chip_data = &pdata->chip[chip_index]; adapter = i2c_get_adapter(pdata->i2c_adapter_id); - if (adapter == NULL) { + if (!adapter) { /* Eek, no such I2C adapter! Bail out. */ dev_warn(dev, "Chip at i2c address 0x%x: Invalid i2c adapter %d\n", plat_chip_data->addr, pdata->i2c_adapter_id); diff --git a/drivers/mfd/intel_soc_pmic_core.c b/drivers/mfd/intel_soc_pmic_core.c index 2720922f90b..df7b0642a5b 100644 --- a/drivers/mfd/intel_soc_pmic_core.c +++ b/drivers/mfd/intel_soc_pmic_core.c @@ -115,6 +115,7 @@ static void intel_soc_pmic_shutdown(struct i2c_client *i2c) return; } +#if defined(CONFIG_PM_SLEEP) static int intel_soc_pmic_suspend(struct device *dev) { struct intel_soc_pmic *pmic = dev_get_drvdata(dev); @@ -132,6 +133,7 @@ static int intel_soc_pmic_resume(struct device *dev) return 0; } +#endif static SIMPLE_DEV_PM_OPS(intel_soc_pmic_pm_ops, intel_soc_pmic_suspend, intel_soc_pmic_resume); diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c index 7d8482ff586..f35d4280b2f 100644 --- a/drivers/mfd/lpc_ich.c +++ b/drivers/mfd/lpc_ich.c @@ -54,6 +54,7 @@ * document number TBD : Avoton SoC * document number TBD : Coleto Creek * document number TBD : Wildcat Point-LP + * document number TBD : 9 Series */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -216,6 +217,8 @@ enum lpc_chipsets { LPC_BAYTRAIL, /* Bay Trail SoC */ LPC_COLETO, /* Coleto Creek */ LPC_WPT_LP, /* Wildcat Point-LP */ + LPC_BRASWELL, /* Braswell SoC */ + LPC_9S, /* 9 Series */ }; static struct lpc_ich_info lpc_chipset_info[] = { @@ -519,6 +522,14 @@ static struct lpc_ich_info lpc_chipset_info[] = { .name = "Wildcat Point_LP", .iTCO_version = 2, }, + [LPC_BRASWELL] = { + .name = "Braswell SoC", + .iTCO_version = 3, + }, + [LPC_9S] = { + .name = "9 Series", + .iTCO_version = 2, + }, }; /* @@ -745,6 +756,12 @@ static const struct pci_device_id lpc_ich_ids[] = { { PCI_VDEVICE(INTEL, 0x9cc6), LPC_WPT_LP}, { PCI_VDEVICE(INTEL, 0x9cc7), LPC_WPT_LP}, { PCI_VDEVICE(INTEL, 0x9cc9), LPC_WPT_LP}, + { PCI_VDEVICE(INTEL, 0x229c), LPC_BRASWELL}, + { PCI_VDEVICE(INTEL, 0x8cc1), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc2), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc3), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc4), LPC_9S}, + { PCI_VDEVICE(INTEL, 0x8cc6), LPC_9S}, { 0, }, /* End of list */ }; MODULE_DEVICE_TABLE(pci, lpc_ich_ids); diff --git a/drivers/mfd/lpc_sch.c b/drivers/mfd/lpc_sch.c index 4ee755034f3..c980da479a3 100644 --- a/drivers/mfd/lpc_sch.c +++ b/drivers/mfd/lpc_sch.c @@ -7,6 +7,7 @@ * Configuration Registers. * * Copyright (c) 2010 CompuLab Ltd + * Copyright (c) 2014 Intel Corp. * Author: Denis Turischev <denis@compulab.co.il> * * This program is free software; you can redistribute it and/or modify @@ -17,10 +18,6 @@ * 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; see the file COPYING. If not, write to - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <linux/kernel.h> @@ -37,123 +34,165 @@ #define GPIO_IO_SIZE 64 #define GPIO_IO_SIZE_CENTERTON 128 +/* Intel Quark X1000 GPIO IRQ Number */ +#define GPIO_IRQ_QUARK_X1000 9 + #define WDTBASE 0x84 #define WDT_IO_SIZE 64 -static struct resource smbus_sch_resource = { - .flags = IORESOURCE_IO, -}; - -static struct resource gpio_sch_resource = { - .flags = IORESOURCE_IO, -}; - -static struct resource wdt_sch_resource = { - .flags = IORESOURCE_IO, +enum sch_chipsets { + LPC_SCH = 0, /* Intel Poulsbo SCH */ + LPC_ITC, /* Intel Tunnel Creek */ + LPC_CENTERTON, /* Intel Centerton */ + LPC_QUARK_X1000, /* Intel Quark X1000 */ }; -static struct mfd_cell lpc_sch_cells[3]; - -static struct mfd_cell isch_smbus_cell = { - .name = "isch_smbus", - .num_resources = 1, - .resources = &smbus_sch_resource, - .ignore_resource_conflicts = true, +struct lpc_sch_info { + unsigned int io_size_smbus; + unsigned int io_size_gpio; + unsigned int io_size_wdt; + int irq_gpio; }; -static struct mfd_cell sch_gpio_cell = { - .name = "sch_gpio", - .num_resources = 1, - .resources = &gpio_sch_resource, - .ignore_resource_conflicts = true, -}; - -static struct mfd_cell wdt_sch_cell = { - .name = "ie6xx_wdt", - .num_resources = 1, - .resources = &wdt_sch_resource, - .ignore_resource_conflicts = true, +static struct lpc_sch_info sch_chipset_info[] = { + [LPC_SCH] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_ITC] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + .io_size_wdt = WDT_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_CENTERTON] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE_CENTERTON, + .io_size_wdt = WDT_IO_SIZE, + .irq_gpio = -1, + }, + [LPC_QUARK_X1000] = { + .io_size_gpio = GPIO_IO_SIZE, + .irq_gpio = GPIO_IRQ_QUARK_X1000, + }, }; static const struct pci_device_id lpc_sch_ids[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC) }, - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC), LPC_SCH }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC), LPC_ITC }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB), LPC_CENTERTON }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB), LPC_QUARK_X1000 }, { 0, } }; MODULE_DEVICE_TABLE(pci, lpc_sch_ids); -static int lpc_sch_probe(struct pci_dev *dev, - const struct pci_device_id *id) +#define LPC_NO_RESOURCE 1 +#define LPC_SKIP_RESOURCE 2 + +static int lpc_sch_get_io(struct pci_dev *pdev, int where, const char *name, + struct resource *res, int size) { unsigned int base_addr_cfg; unsigned short base_addr; - int i, cells = 0; - int ret; - pci_read_config_dword(dev, SMBASE, &base_addr_cfg); + if (size == 0) + return LPC_NO_RESOURCE; + + pci_read_config_dword(pdev, where, &base_addr_cfg); base_addr = 0; if (!(base_addr_cfg & (1 << 31))) - dev_warn(&dev->dev, "Decode of the SMBus I/O range disabled\n"); + dev_warn(&pdev->dev, "Decode of the %s I/O range disabled\n", + name); else base_addr = (unsigned short)base_addr_cfg; if (base_addr == 0) { - dev_warn(&dev->dev, "I/O space for SMBus uninitialized\n"); - } else { - lpc_sch_cells[cells++] = isch_smbus_cell; - smbus_sch_resource.start = base_addr; - smbus_sch_resource.end = base_addr + SMBUS_IO_SIZE - 1; + dev_warn(&pdev->dev, "I/O space for %s uninitialized\n", name); + return LPC_SKIP_RESOURCE; } - pci_read_config_dword(dev, GPIOBASE, &base_addr_cfg); - base_addr = 0; - if (!(base_addr_cfg & (1 << 31))) - dev_warn(&dev->dev, "Decode of the GPIO I/O range disabled\n"); - else - base_addr = (unsigned short)base_addr_cfg; + res->start = base_addr; + res->end = base_addr + size - 1; + res->flags = IORESOURCE_IO; - if (base_addr == 0) { - dev_warn(&dev->dev, "I/O space for GPIO uninitialized\n"); - } else { - lpc_sch_cells[cells++] = sch_gpio_cell; - gpio_sch_resource.start = base_addr; - if (id->device == PCI_DEVICE_ID_INTEL_CENTERTON_ILB) - gpio_sch_resource.end = base_addr + GPIO_IO_SIZE_CENTERTON - 1; - else - gpio_sch_resource.end = base_addr + GPIO_IO_SIZE - 1; - } + return 0; +} - if (id->device == PCI_DEVICE_ID_INTEL_ITC_LPC - || id->device == PCI_DEVICE_ID_INTEL_CENTERTON_ILB) { - pci_read_config_dword(dev, WDTBASE, &base_addr_cfg); - base_addr = 0; - if (!(base_addr_cfg & (1 << 31))) - dev_warn(&dev->dev, "Decode of the WDT I/O range disabled\n"); - else - base_addr = (unsigned short)base_addr_cfg; - if (base_addr == 0) - dev_warn(&dev->dev, "I/O space for WDT uninitialized\n"); - else { - lpc_sch_cells[cells++] = wdt_sch_cell; - wdt_sch_resource.start = base_addr; - wdt_sch_resource.end = base_addr + WDT_IO_SIZE - 1; - } - } +static int lpc_sch_populate_cell(struct pci_dev *pdev, int where, + const char *name, int size, int irq, + int id, struct mfd_cell *cell) +{ + struct resource *res; + int ret; - if (WARN_ON(cells > ARRAY_SIZE(lpc_sch_cells))) { - dev_err(&dev->dev, "Cell count exceeds array size"); - return -ENODEV; - } + res = devm_kcalloc(&pdev->dev, 2, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + ret = lpc_sch_get_io(pdev, where, name, res, size); + if (ret) + return ret; + + memset(cell, 0, sizeof(*cell)); + + cell->name = name; + cell->resources = res; + cell->num_resources = 1; + cell->ignore_resource_conflicts = true; + cell->id = id; + + /* Check if we need to add an IRQ resource */ + if (irq < 0) + return 0; + + res++; + + res->start = irq; + res->end = irq; + res->flags = IORESOURCE_IRQ; + + cell->num_resources++; + + return 0; +} + +static int lpc_sch_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct mfd_cell lpc_sch_cells[3]; + struct lpc_sch_info *info = &sch_chipset_info[id->driver_data]; + unsigned int cells = 0; + int ret; + + ret = lpc_sch_populate_cell(dev, SMBASE, "isch_smbus", + info->io_size_smbus, -1, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, GPIOBASE, "sch_gpio", + info->io_size_gpio, info->irq_gpio, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, WDTBASE, "ie6xx_wdt", + info->io_size_wdt, -1, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; if (cells == 0) { dev_err(&dev->dev, "All decode registers disabled.\n"); return -ENODEV; } - for (i = 0; i < cells; i++) - lpc_sch_cells[i].id = id->device; - ret = mfd_add_devices(&dev->dev, 0, lpc_sch_cells, cells, NULL, 0, NULL); if (ret) mfd_remove_devices(&dev->dev); diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 4a5e885383f..de96b7fb1f6 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -26,6 +26,87 @@ #include <linux/mfd/max14577.h> #include <linux/mfd/max14577-private.h> +/* + * Table of valid charger currents for different Maxim chipsets. + * It is placed here because it is used by both charger and regulator driver. + */ +const struct maxim_charger_current maxim_charger_currents[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, + [MAXIM_DEVICE_TYPE_MAX14577] = { + .min = MAX14577_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX14577_CHARGER_CURRENT_LIMIT_MAX, + }, + [MAXIM_DEVICE_TYPE_MAX77836] = { + .min = MAX77836_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX77836_CHARGER_CURRENT_LIMIT_MAX, + }, +}; +EXPORT_SYMBOL_GPL(maxim_charger_currents); + +/* + * maxim_charger_calc_reg_current - Calculate register value for current + * @limits: constraints for charger, matching the MBCICHWRC register + * @min_ua: minimal requested current, micro Amps + * @max_ua: maximum requested current, micro Amps + * @dst: destination to store calculated register value + * + * Calculates the value of MBCICHWRC (Fast Battery Charge Current) register + * for given current and stores it under pointed 'dst'. The stored value + * combines low bit (MBCICHWRCL) and high bits (MBCICHWRCH). It is also + * properly shifted. + * + * The calculated register value matches the current which: + * - is always between <limits.min, limits.max>; + * - is always less or equal to max_ua; + * - is the highest possible value; + * - may be lower than min_ua. + * + * On success returns 0. On error returns -EINVAL (requested min/max current + * is outside of given charger limits) and 'dst' is not set. + */ +int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst) +{ + unsigned int current_bits = 0xf; + + if (min_ua > max_ua) + return -EINVAL; + + if (min_ua > limits->max || max_ua < limits->min) + return -EINVAL; + + if (max_ua < limits->high_start) { + /* + * Less than high_start, so set the minimal current + * (turn Low Bit off, 0 as high bits). + */ + *dst = 0x0; + return 0; + } + + /* max_ua is in range: <high_start, infinite>, cut it to limits.max */ + max_ua = min(limits->max, max_ua); + max_ua -= limits->high_start; + /* + * There is no risk of overflow 'max_ua' here because: + * - max_ua >= limits.high_start + * - BUILD_BUG checks that 'limits' are: max >= high_start + high_step + */ + current_bits = max_ua / limits->high_step; + + /* Turn Low Bit on (use range <limits.high_start, limits.max>) ... */ + *dst = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + /* and set proper High Bits */ + *dst |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + + return 0; +} +EXPORT_SYMBOL_GPL(maxim_charger_calc_reg_current); + static const struct mfd_cell max14577_devs[] = { { .name = "max14577-muic", @@ -35,7 +116,10 @@ static const struct mfd_cell max14577_devs[] = { .name = "max14577-regulator", .of_compatible = "maxim,max14577-regulator", }, - { .name = "max14577-charger", }, + { + .name = "max14577-charger", + .of_compatible = "maxim,max14577-charger", + }, }; static const struct mfd_cell max77836_devs[] = { @@ -372,8 +456,7 @@ static int max14577_i2c_probe(struct i2c_client *i2c, } ret = mfd_add_devices(max14577->dev, -1, mfd_devs, - mfd_devs_size, NULL, 0, - regmap_irq_get_domain(max14577->irq_data)); + mfd_devs_size, NULL, 0, NULL); if (ret < 0) goto err_mfd; @@ -463,6 +546,20 @@ static int __init max14577_i2c_init(void) BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + /* Valid charger current values must be provided for each chipset */ + BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); + + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX14577_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX77836_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); diff --git a/drivers/mfd/max77686.c b/drivers/mfd/max77686.c index 86e552348db..929795eae9f 100644 --- a/drivers/mfd/max77686.c +++ b/drivers/mfd/max77686.c @@ -52,7 +52,7 @@ static const struct mfd_cell max77802_devs[] = { static bool max77802_pmic_is_accessible_reg(struct device *dev, unsigned int reg) { - return (reg >= MAX77802_REG_DEVICE_ID && reg < MAX77802_REG_PMIC_END); + return reg < MAX77802_REG_PMIC_END; } static bool max77802_rtc_is_accessible_reg(struct device *dev, diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c index 249c139ef04..cf008f45968 100644 --- a/drivers/mfd/max77693.c +++ b/drivers/mfd/max77693.c @@ -44,9 +44,12 @@ static const struct mfd_cell max77693_devs[] = { { .name = "max77693-pmic", }, { .name = "max77693-charger", }, - { .name = "max77693-flash", }, { .name = "max77693-muic", }, { .name = "max77693-haptic", }, + { + .name = "max77693-flash", + .of_compatible = "maxim,max77693-flash", + }, }; static const struct regmap_config max77693_regmap_config = { diff --git a/drivers/mfd/max8925-i2c.c b/drivers/mfd/max8925-i2c.c index ecbe78ead3b..c880c895c5a 100644 --- a/drivers/mfd/max8925-i2c.c +++ b/drivers/mfd/max8925-i2c.c @@ -37,7 +37,7 @@ static inline int max8925_read_device(struct i2c_client *i2c, static inline int max8925_write_device(struct i2c_client *i2c, int reg, int bytes, void *src) { - unsigned char buf[bytes + 1]; + unsigned char buf[9]; int ret; buf[0] = (unsigned char)reg; diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 2b6bc868cd3..64dde5d24b3 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -36,6 +36,9 @@ #define MC34708_REVISION_FIN (0x07 << 6) #define MC34708_REVISION_FAB (0x07 << 9) +#define MC13XXX_PWRCTRL 15 +#define MC13XXX_PWRCTRL_WDIRESET (1 << 12) + #define MC13XXX_ADC1 44 #define MC13XXX_ADC1_ADEN (1 << 0) #define MC13XXX_ADC1_RAND (1 << 1) @@ -416,6 +419,11 @@ int mc13xxx_common_init(struct device *dev) mc13xxx->variant->print_revision(mc13xxx, revision); + ret = mc13xxx_reg_rmw(mc13xxx, MC13XXX_PWRCTRL, + MC13XXX_PWRCTRL_WDIRESET, MC13XXX_PWRCTRL_WDIRESET); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(mc13xxx->irqs); i++) { mc13xxx->irqs[i].reg_offset = i / MC13XXX_IRQ_PER_REG; mc13xxx->irqs[i].mask = BIT(i % MC13XXX_IRQ_PER_REG); diff --git a/drivers/mfd/menelaus.c b/drivers/mfd/menelaus.c index 5e2667afe2b..9f01aef539d 100644 --- a/drivers/mfd/menelaus.c +++ b/drivers/mfd/menelaus.c @@ -466,8 +466,6 @@ static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV, struct i2c_client *c = the_menelaus->client; mutex_lock(&the_menelaus->lock); - if (!vtg) - goto set_voltage; ret = menelaus_read_reg(vtg->vtg_reg); if (ret < 0) @@ -482,7 +480,6 @@ static int menelaus_set_voltage(const struct menelaus_vtg *vtg, int mV, ret = menelaus_write_reg(vtg->vtg_reg, val); if (ret < 0) goto out; -set_voltage: ret = menelaus_write_reg(vtg->mode_reg, mode); out: mutex_unlock(&the_menelaus->lock); @@ -1186,7 +1183,7 @@ static int menelaus_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct menelaus_chip *menelaus; - int rev = 0, val; + int rev = 0; int err = 0; struct menelaus_platform_data *menelaus_pdata = dev_get_platdata(&client->dev); @@ -1239,10 +1236,10 @@ static int menelaus_probe(struct i2c_client *client, pr_info("Menelaus rev %d.%d\n", rev >> 4, rev & 0x0f); - val = menelaus_read_reg(MENELAUS_VCORE_CTRL1); - if (val < 0) + err = menelaus_read_reg(MENELAUS_VCORE_CTRL1); + if (err < 0) goto fail; - if (val & (1 << 7)) + if (err & BIT(7)) menelaus->vcore_hw_mode = 1; else menelaus->vcore_hw_mode = 0; diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 892d343193a..f3338fe9d06 100644 --- a/drivers/mfd/mfd-core.c +++ b/drivers/mfd/mfd-core.c @@ -78,6 +78,44 @@ static int mfd_platform_add_cell(struct platform_device *pdev, return 0; } +#if IS_ENABLED(CONFIG_ACPI) +static void mfd_acpi_add_device(const struct mfd_cell *cell, + struct platform_device *pdev) +{ + struct acpi_device *parent_adev; + struct acpi_device *adev; + + parent_adev = ACPI_COMPANION(pdev->dev.parent); + if (!parent_adev) + return; + + /* + * MFD child device gets its ACPI handle either from the ACPI + * device directly under the parent that matches the acpi_pnpid or + * it will use the parent handle if is no acpi_pnpid is given. + */ + adev = parent_adev; + if (cell->acpi_pnpid) { + struct acpi_device_id ids[2] = {}; + struct acpi_device *child_adev; + + strlcpy(ids[0].id, cell->acpi_pnpid, sizeof(ids[0].id)); + list_for_each_entry(child_adev, &parent_adev->children, node) + if (acpi_match_device_ids(child_adev, ids)) { + adev = child_adev; + break; + } + } + + ACPI_COMPANION_SET(&pdev->dev, adev); +} +#else +static inline void mfd_acpi_add_device(const struct mfd_cell *cell, + struct platform_device *pdev) +{ +} +#endif + static int mfd_add_device(struct device *parent, int id, const struct mfd_cell *cell, atomic_t *usage_count, struct resource *mem_base, @@ -101,6 +139,7 @@ static int mfd_add_device(struct device *parent, int id, pdev->dev.type = &mfd_dev_type; pdev->dev.dma_mask = parent->dma_mask; pdev->dev.dma_parms = parent->dma_parms; + pdev->dev.coherent_dma_mask = parent->coherent_dma_mask; ret = regulator_bulk_register_supply_alias( &pdev->dev, cell->parent_supplies, @@ -118,6 +157,8 @@ static int mfd_add_device(struct device *parent, int id, } } + mfd_acpi_add_device(cell, pdev); + if (cell->pdata_size) { ret = platform_device_add_data(pdev, cell->platform_data, cell->pdata_size); diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c index c87f7a0a53f..43664eb69c9 100644 --- a/drivers/mfd/pcf50633-core.c +++ b/drivers/mfd/pcf50633-core.c @@ -106,10 +106,7 @@ static ssize_t show_dump_regs(struct device *dev, struct device_attribute *attr, } else dump[n1] = pcf50633_reg_read(pcf, n + n1); - hex_dump_to_buffer(dump, sizeof(dump), 16, 1, buf1, 128, 0); - buf1 += strlen(buf1); - *buf1++ = '\n'; - *buf1 = '\0'; + buf1 += sprintf(buf1, "%*ph\n", (int)sizeof(dump), dump); } return buf1 - buf; @@ -195,8 +192,9 @@ static int pcf50633_probe(struct i2c_client *client, const struct i2c_device_id *ids) { struct pcf50633 *pcf; + struct platform_device *pdev; struct pcf50633_platform_data *pdata = dev_get_platdata(&client->dev); - int i, ret; + int i, j, ret; int version, variant; if (!client->irq) { @@ -243,9 +241,6 @@ static int pcf50633_probe(struct i2c_client *client, for (i = 0; i < PCF50633_NUM_REGULATORS; i++) { - struct platform_device *pdev; - int j; - pdev = platform_device_alloc("pcf50633-regulator", i); if (!pdev) return -ENOMEM; @@ -253,25 +248,31 @@ static int pcf50633_probe(struct i2c_client *client, pdev->dev.parent = pcf->dev; ret = platform_device_add_data(pdev, &pdata->reg_init_data[i], sizeof(pdata->reg_init_data[i])); - if (ret) { - platform_device_put(pdev); - for (j = 0; j < i; j++) - platform_device_put(pcf->regulator_pdev[j]); - return ret; - } - pcf->regulator_pdev[i] = pdev; + if (ret) + goto err; + + ret = platform_device_add(pdev); + if (ret) + goto err; - platform_device_add(pdev); + pcf->regulator_pdev[i] = pdev; } ret = sysfs_create_group(&client->dev.kobj, &pcf_attr_group); if (ret) - dev_err(pcf->dev, "error creating sysfs entries\n"); + dev_warn(pcf->dev, "error creating sysfs entries\n"); if (pdata->probe_done) pdata->probe_done(pcf); return 0; + +err: + platform_device_put(pdev); + for (j = 0; j < i; j++) + platform_device_put(pcf->regulator_pdev[j]); + + return ret; } static int pcf50633_remove(struct i2c_client *client) diff --git a/drivers/mfd/qcom-spmi-pmic.c b/drivers/mfd/qcom-spmi-pmic.c new file mode 100644 index 00000000000..4b8beb2a157 --- /dev/null +++ b/drivers/mfd/qcom-spmi-pmic.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/spmi.h> +#include <linux/regmap.h> +#include <linux/of_platform.h> + +static const struct regmap_config spmi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xffff, + .fast_io = true, +}; + +static int pmic_spmi_probe(struct spmi_device *sdev) +{ + struct device_node *root = sdev->dev.of_node; + struct regmap *regmap; + + regmap = devm_regmap_init_spmi_ext(sdev, &spmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return of_platform_populate(root, NULL, NULL, &sdev->dev); +} + +static void pmic_spmi_remove(struct spmi_device *sdev) +{ + of_platform_depopulate(&sdev->dev); +} + +static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,spmi-pmic" }, + { .compatible = "qcom,pm8941" }, + { .compatible = "qcom,pm8841" }, + { .compatible = "qcom,pma8084" }, + { } +}; +MODULE_DEVICE_TABLE(of, pmic_spmi_id_table); + +static struct spmi_driver pmic_spmi_driver = { + .probe = pmic_spmi_probe, + .remove = pmic_spmi_remove, + .driver = { + .name = "pmic-spmi", + .of_match_table = pmic_spmi_id_table, + }, +}; +module_spmi_driver(pmic_spmi_driver); + +MODULE_DESCRIPTION("Qualcomm SPMI PMIC driver"); +MODULE_ALIAS("spmi:spmi-pmic"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Josh Cartwright <joshc@codeaurora.org>"); +MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>"); diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c new file mode 100644 index 00000000000..bd021506987 --- /dev/null +++ b/drivers/mfd/rk808.c @@ -0,0 +1,275 @@ +/* + * MFD core driver for Rockchip RK808 + * + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * + * Author: Chris Zhong <zyw@rock-chips.com> + * Author: Zhang Qing <zhangqing@rock-chips.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mfd/rk808.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/regmap.h> + +struct rk808_reg_data { + int addr; + int mask; + int value; +}; + +static bool rk808_is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* + * Notes: + * - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but + * we don't use that feature. It's better to cache. + * - It's unlikely we care that RK808_DEVCTRL_REG is volatile since + * bits are cleared in case when we shutoff anyway, but better safe. + */ + + switch (reg) { + case RK808_SECONDS_REG ... RK808_WEEKS_REG: + case RK808_RTC_STATUS_REG: + case RK808_VB_MON_REG: + case RK808_THERMAL_REG: + case RK808_DCDC_UV_STS_REG: + case RK808_LDO_UV_STS_REG: + case RK808_DCDC_PG_REG: + case RK808_LDO_PG_REG: + case RK808_DEVCTRL_REG: + case RK808_INT_STS_REG1: + case RK808_INT_STS_REG2: + return true; + } + + return false; +} + +static const struct regmap_config rk808_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = RK808_IO_POL_REG, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = rk808_is_volatile_reg, +}; + +static struct resource rtc_resources[] = { + { + .start = RK808_IRQ_RTC_ALARM, + .end = RK808_IRQ_RTC_ALARM, + .flags = IORESOURCE_IRQ, + } +}; + +static const struct mfd_cell rk808s[] = { + { .name = "rk808-clkout", }, + { .name = "rk808-regulator", }, + { + .name = "rk808-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), + .resources = &rtc_resources[0], + }, +}; + +static const struct rk808_reg_data pre_init_reg[] = { + { RK808_BUCK3_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_150MA }, + { RK808_BUCK4_CONFIG_REG, BUCK_ILMIN_MASK, BUCK_ILMIN_200MA }, + { RK808_BOOST_CONFIG_REG, BOOST_ILMIN_MASK, BOOST_ILMIN_100MA }, + { RK808_BUCK1_CONFIG_REG, BUCK1_RATE_MASK, BUCK_ILMIN_200MA }, + { RK808_BUCK2_CONFIG_REG, BUCK2_RATE_MASK, BUCK_ILMIN_200MA }, + { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | + VB_LO_SEL_3500MV }, +}; + +static const struct regmap_irq rk808_irqs[] = { + /* INT_STS */ + [RK808_IRQ_VOUT_LO] = { + .mask = RK808_IRQ_VOUT_LO_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_VB_LO] = { + .mask = RK808_IRQ_VB_LO_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_PWRON] = { + .mask = RK808_IRQ_PWRON_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_PWRON_LP] = { + .mask = RK808_IRQ_PWRON_LP_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_HOTDIE] = { + .mask = RK808_IRQ_HOTDIE_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_RTC_ALARM] = { + .mask = RK808_IRQ_RTC_ALARM_MSK, + .reg_offset = 0, + }, + [RK808_IRQ_RTC_PERIOD] = { + .mask = RK808_IRQ_RTC_PERIOD_MSK, + .reg_offset = 0, + }, + + /* INT_STS2 */ + [RK808_IRQ_PLUG_IN_INT] = { + .mask = RK808_IRQ_PLUG_IN_INT_MSK, + .reg_offset = 1, + }, + [RK808_IRQ_PLUG_OUT_INT] = { + .mask = RK808_IRQ_PLUG_OUT_INT_MSK, + .reg_offset = 1, + }, +}; + +static struct regmap_irq_chip rk808_irq_chip = { + .name = "rk808", + .irqs = rk808_irqs, + .num_irqs = ARRAY_SIZE(rk808_irqs), + .num_regs = 2, + .irq_reg_stride = 2, + .status_base = RK808_INT_STS_REG1, + .mask_base = RK808_INT_STS_MSK_REG1, + .ack_base = RK808_INT_STS_REG1, + .init_ack_masked = true, +}; + +static struct i2c_client *rk808_i2c_client; +static void rk808_device_shutdown(void) +{ + int ret; + struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client); + + if (!rk808) { + dev_warn(&rk808_i2c_client->dev, + "have no rk808, so do nothing here\n"); + return; + } + + ret = regmap_update_bits(rk808->regmap, + RK808_DEVCTRL_REG, + DEV_OFF_RST, DEV_OFF_RST); + if (ret) + dev_err(&rk808_i2c_client->dev, "power off error!\n"); +} + +static int rk808_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct rk808 *rk808; + int pm_off = 0; + int ret; + int i; + + if (!client->irq) { + dev_err(&client->dev, "No interrupt support, no core IRQ\n"); + return -EINVAL; + } + + rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL); + if (!rk808) + return -ENOMEM; + + rk808->regmap = devm_regmap_init_i2c(client, &rk808_regmap_config); + if (IS_ERR(rk808->regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(rk808->regmap); + } + + for (i = 0; i < ARRAY_SIZE(pre_init_reg); i++) { + ret = regmap_update_bits(rk808->regmap, pre_init_reg[i].addr, + pre_init_reg[i].mask, + pre_init_reg[i].value); + if (ret) { + dev_err(&client->dev, + "0x%x write err\n", pre_init_reg[i].addr); + return ret; + } + } + + ret = regmap_add_irq_chip(rk808->regmap, client->irq, + IRQF_ONESHOT, -1, + &rk808_irq_chip, &rk808->irq_data); + if (ret) { + dev_err(&client->dev, "Failed to add irq_chip %d\n", ret); + return ret; + } + + rk808->i2c = client; + i2c_set_clientdata(client, rk808); + + ret = mfd_add_devices(&client->dev, -1, + rk808s, ARRAY_SIZE(rk808s), + NULL, 0, regmap_irq_get_domain(rk808->irq_data)); + if (ret) { + dev_err(&client->dev, "failed to add MFD devices %d\n", ret); + goto err_irq; + } + + pm_off = of_property_read_bool(np, + "rockchip,system-power-controller"); + if (pm_off && !pm_power_off) { + rk808_i2c_client = client; + pm_power_off = rk808_device_shutdown; + } + + return 0; + +err_irq: + regmap_del_irq_chip(client->irq, rk808->irq_data); + return ret; +} + +static int rk808_remove(struct i2c_client *client) +{ + struct rk808 *rk808 = i2c_get_clientdata(client); + + regmap_del_irq_chip(client->irq, rk808->irq_data); + mfd_remove_devices(&client->dev); + pm_power_off = NULL; + + return 0; +} + +static struct of_device_id rk808_of_match[] = { + { .compatible = "rockchip,rk808" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rk808_of_match); + +static const struct i2c_device_id rk808_ids[] = { + { "rk808" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, rk808_ids); + +static struct i2c_driver rk808_i2c_driver = { + .driver = { + .name = "rk808", + .of_match_table = rk808_of_match, + }, + .probe = rk808_probe, + .remove = rk808_remove, + .id_table = rk808_ids, +}; + +module_i2c_driver(rk808_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>"); +MODULE_AUTHOR("Zhang Qing <zhangqing@rock-chips.com>"); +MODULE_DESCRIPTION("RK808 PMIC driver"); diff --git a/drivers/mfd/rn5t618.c b/drivers/mfd/rn5t618.c new file mode 100644 index 00000000000..666857192db --- /dev/null +++ b/drivers/mfd/rn5t618.c @@ -0,0 +1,134 @@ +/* + * MFD core driver for Ricoh RN5T618 PMIC + * + * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rn5t618.h> +#include <linux/module.h> +#include <linux/regmap.h> + +static const struct mfd_cell rn5t618_cells[] = { + { .name = "rn5t618-regulator" }, + { .name = "rn5t618-wdt" }, +}; + +static bool rn5t618_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RN5T618_WATCHDOGCNT: + case RN5T618_DCIRQ: + case RN5T618_ILIMDATAH ... RN5T618_AIN0DATAL: + case RN5T618_IR_ADC1 ... RN5T618_IR_ADC3: + case RN5T618_IR_GPR: + case RN5T618_IR_GPF: + case RN5T618_MON_IOIN: + case RN5T618_INTMON: + return true; + default: + return false; + } +} + +static const struct regmap_config rn5t618_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = rn5t618_volatile_reg, + .max_register = RN5T618_MAX_REG, + .cache_type = REGCACHE_RBTREE, +}; + +static struct rn5t618 *rn5t618_pm_power_off; + +static void rn5t618_power_off(void) +{ + /* disable automatic repower-on */ + regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_REPCNT, + RN5T618_REPCNT_REPWRON, 0); + /* start power-off sequence */ + regmap_update_bits(rn5t618_pm_power_off->regmap, RN5T618_SLPCNT, + RN5T618_SLPCNT_SWPWROFF, RN5T618_SLPCNT_SWPWROFF); +} + +static int rn5t618_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rn5t618 *priv; + int ret; + + priv = devm_kzalloc(&i2c->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(i2c, priv); + + priv->regmap = devm_regmap_init_i2c(i2c, &rn5t618_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(&i2c->dev, "regmap init failed: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(&i2c->dev, -1, rn5t618_cells, + ARRAY_SIZE(rn5t618_cells), NULL, 0, NULL); + if (ret) { + dev_err(&i2c->dev, "failed to add sub-devices: %d\n", ret); + return ret; + } + + if (!pm_power_off) { + rn5t618_pm_power_off = priv; + pm_power_off = rn5t618_power_off; + } + + return 0; +} + +static int rn5t618_i2c_remove(struct i2c_client *i2c) +{ + struct rn5t618 *priv = i2c_get_clientdata(i2c); + + if (priv == rn5t618_pm_power_off) { + rn5t618_pm_power_off = NULL; + pm_power_off = NULL; + } + + mfd_remove_devices(&i2c->dev); + return 0; +} + +static const struct of_device_id rn5t618_of_match[] = { + { .compatible = "ricoh,rn5t618" }, + { } +}; +MODULE_DEVICE_TABLE(of, rn5t618_of_match); + +static const struct i2c_device_id rn5t618_i2c_id[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, rn5t618_i2c_id); + +static struct i2c_driver rn5t618_i2c_driver = { + .driver = { + .name = "rn5t618", + .of_match_table = of_match_ptr(rn5t618_of_match), + }, + .probe = rn5t618_i2c_probe, + .remove = rn5t618_i2c_remove, + .id_table = rn5t618_i2c_id, +}; + +module_i2c_driver(rn5t618_i2c_driver); + +MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); +MODULE_DESCRIPTION("Ricoh RN5T618 MFD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c index d01b8c24923..f2643c221d3 100644 --- a/drivers/mfd/rtsx_pcr.c +++ b/drivers/mfd/rtsx_pcr.c @@ -1197,7 +1197,7 @@ static int rtsx_pci_probe(struct pci_dev *pcidev, pcr->msi_en = msi_en; if (pcr->msi_en) { ret = pci_enable_msi(pcidev); - if (ret < 0) + if (ret) pcr->msi_en = false; } diff --git a/drivers/mfd/rtsx_usb.c b/drivers/mfd/rtsx_usb.c index 71f387ce8cb..9cf98d142d9 100644 --- a/drivers/mfd/rtsx_usb.c +++ b/drivers/mfd/rtsx_usb.c @@ -684,7 +684,7 @@ static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message) struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); - dev_dbg(&intf->dev, "%s called with pm message 0x%04u\n", + dev_dbg(&intf->dev, "%s called with pm message 0x%04x\n", __func__, message.event); /* diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 02027b7f122..6ce6e620035 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -514,9 +514,9 @@ unsigned long sm501_set_clock(struct device *dev, unsigned long mode = smc501_readl(sm->regs + SM501_POWER_MODE_CONTROL); unsigned long gate = smc501_readl(sm->regs + SM501_CURRENT_GATE); unsigned long clock = smc501_readl(sm->regs + SM501_CURRENT_CLOCK); - unsigned char reg; unsigned int pll_reg = 0; unsigned long sm501_freq; /* the actual frequency achieved */ + u64 reg; struct sm501_clock to; diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 02a17c388e8..e2f9df1c0c3 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -249,7 +249,7 @@ int stmpe_set_altfunc(struct stmpe *stmpe, u32 pins, enum stmpe_block block) int af_bits = variant->af_bits; int numregs = DIV_ROUND_UP(stmpe->num_gpios * af_bits, 8); int mask = (1 << af_bits) - 1; - u8 regs[numregs]; + u8 regs[8]; int af, afperreg, ret; if (!variant->get_altfunc) @@ -854,7 +854,7 @@ static irqreturn_t stmpe_irq(int irq, void *data) struct stmpe_variant_info *variant = stmpe->variant; int num = DIV_ROUND_UP(variant->num_irqs, 8); u8 israddr; - u8 isr[num]; + u8 isr[3]; int ret; int i; @@ -1122,7 +1122,12 @@ static void stmpe_of_probe(struct stmpe_platform_data *pdata, if (pdata->id < 0) pdata->id = -1; - pdata->irq_trigger = IRQF_TRIGGER_NONE; + pdata->irq_gpio = of_get_named_gpio_flags(np, "irq-gpio", 0, + &pdata->irq_trigger); + if (gpio_is_valid(pdata->irq_gpio)) + pdata->irq_over_gpio = 1; + else + pdata->irq_trigger = IRQF_TRIGGER_NONE; of_property_read_u32(np, "st,autosleep-timeout", &pdata->autosleep_timeout); diff --git a/drivers/mfd/ti_am335x_tscadc.c b/drivers/mfd/ti_am335x_tscadc.c index dd4bf581622..d877e777cce 100644 --- a/drivers/mfd/ti_am335x_tscadc.c +++ b/drivers/mfd/ti_am335x_tscadc.c @@ -53,11 +53,11 @@ void am335x_tsc_se_set_cache(struct ti_tscadc_dev *tsadc, u32 val) unsigned long flags; spin_lock_irqsave(&tsadc->reg_lock, flags); - tsadc->reg_se_cache = val; + tsadc->reg_se_cache |= val; if (tsadc->adc_waiting) wake_up(&tsadc->reg_se_wait); else if (!tsadc->adc_in_use) - tscadc_writel(tsadc, REG_SE, val); + tscadc_writel(tsadc, REG_SE, tsadc->reg_se_cache); spin_unlock_irqrestore(&tsadc->reg_lock, flags); } @@ -96,6 +96,7 @@ static void am335x_tscadc_need_adc(struct ti_tscadc_dev *tsadc) void am335x_tsc_se_set_once(struct ti_tscadc_dev *tsadc, u32 val) { spin_lock_irq(&tsadc->reg_lock); + tsadc->reg_se_cache |= val; am335x_tscadc_need_adc(tsadc); tscadc_writel(tsadc, REG_SE, val); @@ -241,18 +242,20 @@ static int ti_tscadc_probe(struct platform_device *pdev) tscadc_writel(tscadc, REG_CLKDIV, tscadc->clk_div); /* Set the control register bits */ - ctrl = CNTRLREG_STEPCONFIGWRT | - CNTRLREG_STEPID; - if (tsc_wires > 0) - ctrl |= CNTRLREG_4WIRE | CNTRLREG_TSCENB; + ctrl = CNTRLREG_STEPCONFIGWRT | CNTRLREG_STEPID; tscadc_writel(tscadc, REG_CTRL, ctrl); /* Set register bits for Idle Config Mode */ - if (tsc_wires > 0) + if (tsc_wires > 0) { + tscadc->tsc_wires = tsc_wires; + if (tsc_wires == 5) + ctrl |= CNTRLREG_5WIRE | CNTRLREG_TSCENB; + else + ctrl |= CNTRLREG_4WIRE | CNTRLREG_TSCENB; tscadc_idle_config(tscadc); + } /* Enable the TSC module enable bit */ - ctrl = tscadc_readl(tscadc, REG_CTRL); ctrl |= CNTRLREG_TSCSSENB; tscadc_writel(tscadc, REG_CTRL, ctrl); @@ -324,21 +327,23 @@ static int tscadc_suspend(struct device *dev) static int tscadc_resume(struct device *dev) { struct ti_tscadc_dev *tscadc_dev = dev_get_drvdata(dev); - unsigned int restore, ctrl; + u32 ctrl; pm_runtime_get_sync(dev); /* context restore */ ctrl = CNTRLREG_STEPCONFIGWRT | CNTRLREG_STEPID; - if (tscadc_dev->tsc_cell != -1) - ctrl |= CNTRLREG_TSCENB | CNTRLREG_4WIRE; tscadc_writel(tscadc_dev, REG_CTRL, ctrl); - if (tscadc_dev->tsc_cell != -1) + if (tscadc_dev->tsc_cell != -1) { + if (tscadc_dev->tsc_wires == 5) + ctrl |= CNTRLREG_5WIRE | CNTRLREG_TSCENB; + else + ctrl |= CNTRLREG_4WIRE | CNTRLREG_TSCENB; tscadc_idle_config(tscadc_dev); - restore = tscadc_readl(tscadc_dev, REG_CTRL); - tscadc_writel(tscadc_dev, REG_CTRL, - (restore | CNTRLREG_TSCSSENB)); + } + ctrl |= CNTRLREG_TSCSSENB; + tscadc_writel(tscadc_dev, REG_CTRL, ctrl); tscadc_writel(tscadc_dev, REG_CLKDIV, tscadc_dev->clk_div); diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c index 3cc4c7084b9..a8ee52c95f2 100644 --- a/drivers/mfd/tps65217.c +++ b/drivers/mfd/tps65217.c @@ -146,6 +146,8 @@ EXPORT_SYMBOL_GPL(tps65217_clear_bits); static struct regmap_config tps65217_regmap_config = { .reg_bits = 8, .val_bits = 8, + + .max_register = TPS65217_REG_MAX, }; static const struct of_device_id tps65217_of_match[] = { diff --git a/drivers/mfd/tps65910.c b/drivers/mfd/tps65910.c index f243e75d28f..7612d89850d 100644 --- a/drivers/mfd/tps65910.c +++ b/drivers/mfd/tps65910.c @@ -486,6 +486,11 @@ static int tps65910_i2c_probe(struct i2c_client *i2c, tps65910->i2c_client = i2c; tps65910->id = chip_id; + /* Work around silicon erratum SWCZ010: the tps65910 may miss the + * first I2C transfer. So issue a dummy transfer before the first + * real transfer. + */ + i2c_master_send(i2c, "", 1); tps65910->regmap = devm_regmap_init_i2c(i2c, &tps65910_regmap_config); if (IS_ERR(tps65910->regmap)) { ret = PTR_ERR(tps65910->regmap); diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c index b1dabba763c..1b772ef761c 100644 --- a/drivers/mfd/twl4030-irq.c +++ b/drivers/mfd/twl4030-irq.c @@ -396,13 +396,17 @@ static int twl4030_init_sih_modules(unsigned line) status = twl_i2c_read(sih->module, rxbuf, sih->mask[line].isr_offset, sih->bytes_ixr); if (status < 0) - pr_err("twl4030: err %d initializing %s %s\n", + pr_warn("twl4030: err %d initializing %s %s\n", status, sih->name, "ISR"); - if (!sih->set_cor) + if (!sih->set_cor) { status = twl_i2c_write(sih->module, buf, sih->mask[line].isr_offset, sih->bytes_ixr); + if (status < 0) + pr_warn("twl4030: write failed: %d\n", + status); + } /* * else COR=1 means read sufficed. * (for most SIH modules...) diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c index 4d3ff377149..cf92a6d1c53 100644 --- a/drivers/mfd/twl4030-power.c +++ b/drivers/mfd/twl4030-power.c @@ -627,6 +627,9 @@ static bool twl4030_power_use_poweroff(const struct twl4030_power_data *pdata, if (pdata && pdata->use_poweroff) return true; + if (of_property_read_bool(node, "ti,system-power-controller")) + return true; + if (of_property_read_bool(node, "ti,use_poweroff")) return true; diff --git a/drivers/mfd/twl6040.c b/drivers/mfd/twl6040.c index f9c06c542a4..9687645162a 100644 --- a/drivers/mfd/twl6040.c +++ b/drivers/mfd/twl6040.c @@ -679,6 +679,7 @@ static int twl6040_probe(struct i2c_client *client, if (twl6040->rev < 0) { dev_err(&client->dev, "Failed to read revision register: %d\n", twl6040->rev); + ret = twl6040->rev; goto gpio_err; } diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index fb4d4bb0f47..d6f35bbf795 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -87,6 +87,7 @@ int wm5102_patch(struct arizona *arizona) case 0: wm5102_patch = wm5102_reva_patch; patch_size = ARRAY_SIZE(wm5102_reva_patch); + break; default: wm5102_patch = wm5102_revb_patch; patch_size = ARRAY_SIZE(wm5102_revb_patch); @@ -245,9 +246,6 @@ const struct regmap_irq_chip wm5102_irq = { static const struct reg_default wm5102_reg_default[] = { { 0x00000008, 0x0019 }, /* R8 - Ctrl IF SPI CFG 1 */ { 0x00000009, 0x0001 }, /* R9 - Ctrl IF I2C1 CFG 1 */ - { 0x00000016, 0x0000 }, /* R22 - Write Sequencer Ctrl 0 */ - { 0x00000017, 0x0000 }, /* R23 - Write Sequencer Ctrl 1 */ - { 0x00000018, 0x0000 }, /* R24 - Write Sequencer Ctrl 2 */ { 0x00000020, 0x0000 }, /* R32 - Tone Generator 1 */ { 0x00000021, 0x1000 }, /* R33 - Tone Generator 2 */ { 0x00000022, 0x0000 }, /* R34 - Tone Generator 3 */ @@ -1016,6 +1014,7 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_WRITE_SEQUENCER_CTRL_0: case ARIZONA_WRITE_SEQUENCER_CTRL_1: case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_WRITE_SEQUENCER_CTRL_3: case ARIZONA_WRITE_SEQUENCER_PROM: case ARIZONA_TONE_GENERATOR_1: case ARIZONA_TONE_GENERATOR_2: @@ -1060,6 +1059,8 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_ASYNC_CLOCK_1: case ARIZONA_ASYNC_SAMPLE_RATE_1: case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: case ARIZONA_OUTPUT_SYSTEM_CLOCK: case ARIZONA_OUTPUT_ASYNC_CLOCK: case ARIZONA_RATE_ESTIMATOR_1: @@ -1880,6 +1881,10 @@ static bool wm5102_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case ARIZONA_SOFTWARE_RESET: case ARIZONA_DEVICE_REVISION: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: + case ARIZONA_WRITE_SEQUENCER_CTRL_3: case ARIZONA_OUTPUT_STATUS_1: case ARIZONA_RAW_OUTPUT_STATUS_1: case ARIZONA_SLIMBUS_RX_PORT_STATUS: @@ -1889,8 +1894,13 @@ static bool wm5102_volatile_register(struct device *dev, unsigned int reg) case ARIZONA_SAMPLE_RATE_3_STATUS: case ARIZONA_HAPTICS_STATUS: case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: case ARIZONA_FLL1_NCO_TEST_0: case ARIZONA_FLL2_NCO_TEST_0: + case ARIZONA_DAC_COMP_1: + case ARIZONA_DAC_COMP_2: + case ARIZONA_DAC_COMP_3: + case ARIZONA_DAC_COMP_4: case ARIZONA_FX_CTRL2: case ARIZONA_INTERRUPT_STATUS_1: case ARIZONA_INTERRUPT_STATUS_2: diff --git a/drivers/mfd/wm5110-tables.c b/drivers/mfd/wm5110-tables.c index 9b98ee55918..4642b5b816a 100644 --- a/drivers/mfd/wm5110-tables.c +++ b/drivers/mfd/wm5110-tables.c @@ -666,9 +666,6 @@ static const struct reg_default wm5110_reg_default[] = { { 0x0000000A, 0x0001 }, /* R10 - Ctrl IF I2C2 CFG 1 */ { 0x0000000B, 0x0036 }, /* R11 - Ctrl IF I2C1 CFG 2 */ { 0x0000000C, 0x0036 }, /* R12 - Ctrl IF I2C2 CFG 2 */ - { 0x00000016, 0x0000 }, /* R22 - Write Sequencer Ctrl 0 */ - { 0x00000017, 0x0000 }, /* R23 - Write Sequencer Ctrl 1 */ - { 0x00000018, 0x0000 }, /* R24 - Write Sequencer Ctrl 2 */ { 0x00000020, 0x0000 }, /* R32 - Tone Generator 1 */ { 0x00000021, 0x1000 }, /* R33 - Tone Generator 2 */ { 0x00000022, 0x0000 }, /* R34 - Tone Generator 3 */ @@ -705,6 +702,7 @@ static const struct reg_default wm5110_reg_default[] = { { 0x00000104, 0x0011 }, /* R260 - Sample rate 3 */ { 0x00000112, 0x0305 }, /* R274 - Async clock 1 */ { 0x00000113, 0x0011 }, /* R275 - Async sample rate 1 */ + { 0x00000114, 0x0011 }, /* R276 - Async sample rate 2 */ { 0x00000149, 0x0000 }, /* R329 - Output system clock */ { 0x0000014A, 0x0000 }, /* R330 - Output async clock */ { 0x00000152, 0x0000 }, /* R338 - Rate Estimator 1 */ @@ -1741,6 +1739,8 @@ static bool wm5110_readable_register(struct device *dev, unsigned int reg) case ARIZONA_ASYNC_CLOCK_1: case ARIZONA_ASYNC_SAMPLE_RATE_1: case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: case ARIZONA_OUTPUT_SYSTEM_CLOCK: case ARIZONA_OUTPUT_ASYNC_CLOCK: case ARIZONA_RATE_ESTIMATOR_1: @@ -2815,11 +2815,15 @@ static bool wm5110_volatile_register(struct device *dev, unsigned int reg) switch (reg) { case ARIZONA_SOFTWARE_RESET: case ARIZONA_DEVICE_REVISION: + case ARIZONA_WRITE_SEQUENCER_CTRL_0: + case ARIZONA_WRITE_SEQUENCER_CTRL_1: + case ARIZONA_WRITE_SEQUENCER_CTRL_2: case ARIZONA_HAPTICS_STATUS: case ARIZONA_SAMPLE_RATE_1_STATUS: case ARIZONA_SAMPLE_RATE_2_STATUS: case ARIZONA_SAMPLE_RATE_3_STATUS: case ARIZONA_ASYNC_SAMPLE_RATE_1_STATUS: + case ARIZONA_ASYNC_SAMPLE_RATE_2_STATUS: case ARIZONA_MIC_DETECT_3: case ARIZONA_HEADPHONE_DETECT_2: case ARIZONA_INPUT_ENABLES_STATUS: diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c index e74dedda5b5..a14407edbd8 100644 --- a/drivers/mfd/wm8994-irq.c +++ b/drivers/mfd/wm8994-irq.c @@ -262,8 +262,10 @@ int wm8994_irq_init(struct wm8994 *wm8994) return 0; } +EXPORT_SYMBOL(wm8994_irq_init); void wm8994_irq_exit(struct wm8994 *wm8994) { regmap_del_irq_chip(wm8994->irq, wm8994->irq_data); } +EXPORT_SYMBOL(wm8994_irq_exit); diff --git a/drivers/mfd/wm8994-regmap.c b/drivers/mfd/wm8994-regmap.c index 770a2569646..300e9b6a2e9 100644 --- a/drivers/mfd/wm8994-regmap.c +++ b/drivers/mfd/wm8994-regmap.c @@ -1252,6 +1252,7 @@ struct regmap_config wm1811_regmap_config = { .volatile_reg = wm1811_volatile_register, .readable_reg = wm1811_readable_register, }; +EXPORT_SYMBOL(wm1811_regmap_config); struct regmap_config wm8994_regmap_config = { .reg_bits = 16, @@ -1266,6 +1267,7 @@ struct regmap_config wm8994_regmap_config = { .volatile_reg = wm8994_volatile_register, .readable_reg = wm8994_readable_register, }; +EXPORT_SYMBOL(wm8994_regmap_config); struct regmap_config wm8958_regmap_config = { .reg_bits = 16, @@ -1280,8 +1282,10 @@ struct regmap_config wm8958_regmap_config = { .volatile_reg = wm8958_volatile_register, .readable_reg = wm8958_readable_register, }; +EXPORT_SYMBOL(wm8958_regmap_config); struct regmap_config wm8994_base_regmap_config = { .reg_bits = 16, .val_bits = 16, }; +EXPORT_SYMBOL(wm8994_base_regmap_config); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 73cfcdf28a3..0108c2af005 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -325,11 +325,12 @@ config CHARGER_MANAGER with help of suspend_again support. config CHARGER_MAX14577 - tristate "Maxim MAX14577 MUIC battery charger driver" + tristate "Maxim MAX14577/77836 battery charger driver" depends on MFD_MAX14577 + depends on SYSFS help Say Y to enable support for the battery charger control sysfs and - platform data of MAX14577 MUICs. + platform data of MAX14577/77836 MUICs. config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 3cb4178e397..217da4b2ca8 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -2969,7 +2969,7 @@ static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev) { - unsigned int i, j; + unsigned int i; struct power_supply *psy = dev_get_drvdata(dev); struct ab8500_fg *di; @@ -2978,14 +2978,15 @@ static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev) if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) || is_ab8540(di->parent)) { - for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++) - if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j])) + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + if (device_create_file(dev, + &ab8505_fg_sysfs_psy_attrs[i])) goto sysfs_psy_create_attrs_failed_ab8505; } return 0; sysfs_psy_create_attrs_failed_ab8505: dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n"); - while (j--) + while (i--) device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); return -EIO; diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index e10763e3a1d..e3bacfe3bcd 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -23,6 +23,7 @@ * http://focus.ti.com/docs/prod/folders/print/bq27000.html * http://focus.ti.com/docs/prod/folders/print/bq27500.html * http://www.ti.com/product/bq27425-g1 + * http://www.ti.com/product/BQ27742-G1 */ #include <linux/device.h> @@ -71,6 +72,8 @@ #define BQ27500_FLAG_FC BIT(9) #define BQ27500_FLAG_OTC BIT(15) +#define BQ27742_POWER_AVG 0x76 + /* bq27425 register addresses are same as bq27x00 addresses minus 4 */ #define BQ27425_REG_OFFSET 0x04 #define BQ27425_REG_SOC 0x18 /* Register address plus offset */ @@ -83,7 +86,7 @@ struct bq27x00_access_methods { int (*read)(struct bq27x00_device_info *di, u8 reg, bool single); }; -enum bq27x00_chip { BQ27000, BQ27500, BQ27425}; +enum bq27x00_chip { BQ27000, BQ27500, BQ27425, BQ27742}; struct bq27x00_reg_cache { int temperature; @@ -152,6 +155,24 @@ static enum power_supply_property bq27425_battery_props[] = { POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, }; +static enum power_supply_property bq27742_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, +}; + static unsigned int poll_interval = 360; module_param(poll_interval, uint, 0644); MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \ @@ -176,7 +197,7 @@ static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg, */ static bool bq27xxx_is_chip_version_higher(struct bq27x00_device_info *di) { - if (di->chip == BQ27425 || di->chip == BQ27500) + if (di->chip == BQ27425 || di->chip == BQ27500 || di->chip == BQ27742) return true; return false; } @@ -189,7 +210,7 @@ static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di) { int rsoc; - if (di->chip == BQ27500) + if (di->chip == BQ27500 || di->chip == BQ27742) rsoc = bq27x00_read(di, BQ27500_REG_SOC, false); else if (di->chip == BQ27425) rsoc = bq27x00_read(di, BQ27425_REG_SOC, false); @@ -233,9 +254,11 @@ static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di) { int flags; bool is_bq27500 = di->chip == BQ27500; + bool is_bq27742 = di->chip == BQ27742; bool is_higher = bq27xxx_is_chip_version_higher(di); + bool flags_1b = !(is_bq27500 || is_bq27742); - flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); + flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b); if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI)) return -ENODATA; @@ -414,13 +437,15 @@ static void bq27x00_update(struct bq27x00_device_info *di) struct bq27x00_reg_cache cache = {0, }; bool is_bq27500 = di->chip == BQ27500; bool is_bq27425 = di->chip == BQ27425; + bool is_bq27742 = di->chip == BQ27742; + bool flags_1b = !(is_bq27500 || is_bq27742); - cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); + cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, flags_1b); if ((cache.flags & 0xff) == 0xff) /* read error */ cache.flags = -1; if (cache.flags >= 0) { - if (!is_bq27500 && !is_bq27425 + if (!is_bq27500 && !is_bq27425 && !is_bq27742 && (cache.flags & BQ27000_FLAG_CI)) { dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); cache.capacity = -ENODATA; @@ -432,7 +457,11 @@ static void bq27x00_update(struct bq27x00_device_info *di) cache.health = -ENODATA; } else { cache.capacity = bq27x00_battery_read_rsoc(di); - if (!is_bq27425) { + if (is_bq27742) + cache.time_to_empty = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTE); + else if (!is_bq27425) { cache.energy = bq27x00_battery_read_energy(di); cache.time_to_empty = bq27x00_battery_read_time(di, @@ -450,8 +479,14 @@ static void bq27x00_update(struct bq27x00_device_info *di) cache.temperature = bq27x00_battery_read_temperature(di); if (!is_bq27425) cache.cycle_count = bq27x00_battery_read_cyct(di); - cache.power_avg = - bq27x00_battery_read_pwr_avg(di, BQ27x00_POWER_AVG); + if (is_bq27742) + cache.power_avg = + bq27x00_battery_read_pwr_avg(di, + BQ27742_POWER_AVG); + else + cache.power_avg = + bq27x00_battery_read_pwr_avg(di, + BQ27x00_POWER_AVG); /* We only have to read charge design full once */ if (di->charge_design_full <= 0) @@ -702,6 +737,9 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di) if (di->chip == BQ27425) { di->bat.properties = bq27425_battery_props; di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props); + } else if (di->chip == BQ27742) { + di->bat.properties = bq27742_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27742_battery_props); } else { di->bat.properties = bq27x00_battery_props; di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); @@ -858,6 +896,7 @@ static const struct i2c_device_id bq27x00_id[] = { { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ { "bq27500", BQ27500 }, { "bq27425", BQ27425 }, + { "bq27742", BQ27742 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27x00_id); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 9e4dab46eef..7098a1ce2d3 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -1656,7 +1656,7 @@ static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev) { if (pdev->dev.of_node) return of_cm_parse_desc(&pdev->dev); - return (struct charger_desc *)dev_get_platdata(&pdev->dev); + return dev_get_platdata(&pdev->dev); } static int charger_manager_probe(struct platform_device *pdev) @@ -1677,7 +1677,7 @@ static int charger_manager_probe(struct platform_device *pdev) } } - if (!desc) { + if (IS_ERR(desc)) { dev_err(&pdev->dev, "No platform data (desc) found\n"); return -ENODEV; } @@ -1720,6 +1720,11 @@ static int charger_manager_probe(struct platform_device *pdev) return -EINVAL; } + if (!desc->psy_fuel_gauge) { + dev_err(&pdev->dev, "No fuel gauge power supply defined\n"); + return -EINVAL; + } + /* Counting index only */ while (desc->psy_charger_stat[i]) i++; @@ -1839,6 +1844,13 @@ static int charger_manager_probe(struct platform_device *pdev) device_init_wakeup(&pdev->dev, true); device_set_wakeup_capable(&pdev->dev, false); + /* + * Charger-manager have to check the charging state right after + * tialization of charger-manager and then update current charging + * state. + */ + cm_monitor(); + schedule_work(&setup_polling); return 0; diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index a0024b25219..7536933d0ab 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -55,7 +55,7 @@ static int gpio_charger_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = gpio_get_value_cansleep(pdata->gpio); + val->intval = !!gpio_get_value_cansleep(pdata->gpio); val->intval ^= pdata->gpio_active_low; break; default: diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c index fad2a75b360..0a2bc727702 100644 --- a/drivers/power/max14577_charger.c +++ b/drivers/power/max14577_charger.c @@ -1,7 +1,7 @@ /* - * Battery charger driver for the Maxim 14577 + * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electronics + * Copyright (C) 2013,2014 Samsung Electronics * Krzysztof Kozlowski <k.kozlowski@samsung.com> * * This program is free software; you can redistribute it and/or modify @@ -19,16 +19,44 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/mfd/max14577-private.h> +#include <linux/mfd/max14577.h> struct max14577_charger { struct device *dev; struct max14577 *max14577; struct power_supply charger; - unsigned int charging_state; - unsigned int battery_state; + unsigned int charging_state; + unsigned int battery_state; + + struct max14577_charger_platform_data *pdata; }; +/* + * Helper function for mapping values of STATUS2/CHGTYP register on max14577 + * and max77836 chipsets to enum maxim_muic_charger_type. + */ +static enum max14577_muic_charger_type maxim_get_charger_type( + enum maxim_device_type dev_type, u8 val) { + switch (val) { + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + return val; + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX14577_CHARGER_TYPE_RESERVED: + if (dev_type == MAXIM_DEVICE_TYPE_MAX77836) + val |= 0x8; + return val; + default: + WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val); + return val; + } +} + static int max14577_get_charger_state(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; @@ -89,19 +117,23 @@ static int max14577_get_online(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - switch (reg_data) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + switch (chg_type) { case MAX14577_CHARGER_TYPE_USB: case MAX14577_CHARGER_TYPE_DEDICATED_CHG: case MAX14577_CHARGER_TYPE_SPECIAL_500MA: case MAX14577_CHARGER_TYPE_SPECIAL_1A: case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX77836_CHARGER_TYPE_SPECIAL_BIAS: return 1; case MAX14577_CHARGER_TYPE_NONE: case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: case MAX14577_CHARGER_TYPE_RESERVED: + case MAX77836_CHARGER_TYPE_RESERVED: default: return 0; } @@ -118,10 +150,12 @@ static int max14577_get_battery_health(struct max14577_charger *chg) struct regmap *rmap = chg->max14577->regmap; int state = POWER_SUPPLY_HEALTH_GOOD; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { state = POWER_SUPPLY_HEALTH_DEAD; goto state_set; } @@ -147,15 +181,131 @@ static int max14577_get_present(struct max14577_charger *chg) return 1; } +static int max14577_set_fast_charge_timer(struct max14577_charger *chg, + unsigned long hours) +{ + u8 reg_data; + + switch (hours) { + case 5 ... 7: + reg_data = hours - 3; + break; + case 0: + /* Disable */ + reg_data = 0x7; + break; + default: + dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n", + hours); + return -EINVAL; + } + reg_data <<= CHGCTRL1_TCHW_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data); +} + +static int max14577_init_constant_voltage(struct max14577_charger *chg, + unsigned int uvolt) +{ + u8 reg_data; + + if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN || + uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + return -EINVAL; + + if (uvolt == 4200000) + reg_data = 0x0; + else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + reg_data = 0x1f; + else if (uvolt <= 4280000) { + unsigned int val = uvolt; + + val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN; + val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP; + if (uvolt <= 4180000) + reg_data = 0x1 + val; + else + reg_data = val; /* Fix for gap between 4.18V and 4.22V */ + } else + return -EINVAL; + + reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT; + + return max14577_write_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL3, reg_data); +} + +static int max14577_init_eoc(struct max14577_charger *chg, + unsigned int uamp) +{ + unsigned int current_bits = 0xf; + u8 reg_data; + + switch (chg->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + if (uamp < 5000) + return -EINVAL; /* Requested current is too low */ + + if (uamp >= 7500 && uamp < 10000) + current_bits = 0x0; + else if (uamp <= 50000) { + /* <5000, 7499> and <10000, 50000> */ + current_bits = uamp / 5000; + } else { + uamp = min(uamp, 100000U) - 50000U; + current_bits = 0xa + uamp / 10000; + } + break; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN) + return -EINVAL; /* Requested current is too low */ + + uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); + uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN; + current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP; + break; + } + + reg_data = current_bits << CHGCTRL5_EOCS_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK, + reg_data); +} + +static int max14577_init_fast_charge(struct max14577_charger *chg, + unsigned int uamp) +{ + u8 reg_data; + int ret; + const struct maxim_charger_current *limits = + &maxim_charger_currents[chg->max14577->dev_type]; + + ret = maxim_charger_calc_reg_current(limits, uamp, uamp, ®_data); + if (ret) { + dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp); + return ret; + } + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL4, + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, + reg_data); +} + /* * Sets charger registers to proper and safe default values. * Some of these values are equal to defaults in MAX14577E * data sheet but there are minor differences. */ -static void max14577_charger_reg_init(struct max14577_charger *chg) +static int max14577_charger_reg_init(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + int ret; /* * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) @@ -167,10 +317,6 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, reg_data); - /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ - reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); - /* * Wall-Adapter Rapid Charge, default on * Battery-Charger, default on @@ -179,29 +325,46 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); - /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ - reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); - - /* - * Fast Battery-Charge Current Low, default 200-950mA - * Fast Battery-Charge Current High, from SM-V700: 450mA - */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); - - /* End-of-Charge Current, from SM-V700: 50mA */ - reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); - /* Auto Charging Stop, default off */ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); - /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ - reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt); + if (ret) + return ret; + + ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp); + if (ret) + return ret; + + ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, + MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT); + if (ret) + return ret; + + /* Initialize Overvoltage-Protection Threshold */ + switch (chg->pdata->ovp_uvolt) { + case 7500000: + reg_data = 0x0; + break; + case 6000000: + case 6500000: + case 7000000: + reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000; + break; + default: + dev_err(chg->dev, "Wrong value for OVP: %u\n", + chg->pdata->ovp_uvolt); + return -EINVAL; + } + reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); + + return 0; } /* Support property from charger */ @@ -215,7 +378,11 @@ static enum power_supply_property max14577_charger_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static const char *model_name = "MAX14577"; +static const char * const model_names[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like", + [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577", + [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836", +}; static const char *manufacturer = "Maxim Integrated"; static int max14577_charger_get_property(struct power_supply *psy, @@ -244,7 +411,8 @@ static int max14577_charger_get_property(struct power_supply *psy, val->intval = max14577_get_online(chg); break; case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = model_name; + BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM); + val->strval = model_names[chg->max14577->dev_type]; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = manufacturer; @@ -256,6 +424,110 @@ static int max14577_charger_get_property(struct power_supply *psy, return ret; } +#ifdef CONFIG_OF +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + struct max14577_charger_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No charger OF node\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "maxim,constant-uvolt", + &pdata->constant_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,fast-charge-uamp", + &pdata->fast_charge_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + return pdata; +} +#else /* CONFIG_OF */ +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static ssize_t show_fast_charge_timer(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + u8 reg_data; + int ret; + unsigned int val; + + ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1, + ®_data); + if (ret) + return ret; + + reg_data &= CHGCTRL1_TCHW_MASK; + reg_data >>= CHGCTRL1_TCHW_SHIFT; + switch (reg_data) { + case 0x2 ... 0x4: + val = reg_data + 3; + break; + case 0x7: + val = 0; + break; + default: + val = 5; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_fast_charge_timer(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, val); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR, + show_fast_charge_timer, store_fast_charge_timer); + static int max14577_charger_probe(struct platform_device *pdev) { struct max14577_charger *chg; @@ -270,7 +542,13 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->dev = &pdev->dev; chg->max14577 = max14577; - max14577_charger_reg_init(chg); + chg->pdata = max14577_charger_dt_init(pdev); + if (IS_ERR_OR_NULL(chg->pdata)) + return PTR_ERR(chg->pdata); + + ret = max14577_charger_reg_init(chg); + if (ret) + return ret; chg->charger.name = "max14577-charger", chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, @@ -278,24 +556,47 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), chg->charger.get_property = max14577_charger_get_property, + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create sysfs entry\n"); + return ret; + } + ret = power_supply_register(&pdev->dev, &chg->charger); if (ret) { dev_err(&pdev->dev, "failed: power supply register\n"); - return ret; + goto err; } + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN + + MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf != + MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; } static int max14577_charger_remove(struct platform_device *pdev) { struct max14577_charger *chg = platform_get_drvdata(pdev); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); power_supply_unregister(&chg->charger); return 0; } +static const struct platform_device_id max14577_charger_id[] = { + { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_charger_id); + static struct platform_driver max14577_charger_driver = { .driver = { .owner = THIS_MODULE, @@ -303,9 +604,10 @@ static struct platform_driver max14577_charger_driver = { }, .probe = max14577_charger_probe, .remove = max14577_charger_remove, + .id_table = max14577_charger_id, }; module_platform_driver(max14577_charger_driver); MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 0fbac861080..14d44706327 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -277,7 +277,8 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); #endif /* CONFIG_PM_SLEEP */ static const struct i2c_device_id max17040_id[] = { - { "max17040", 0 }, + { "max17040" }, + { "max77836-battery" }, { } }; MODULE_DEVICE_TABLE(i2c, max17040_id); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index b4513f284bb..a6d45eef64d 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -443,7 +443,7 @@ max8925_power_dt_init(struct platform_device *pdev) if (!nproot) return pdev->dev.platform_data; - np = of_find_node_by_name(nproot, "charger"); + np = of_get_child_by_name(nproot, "charger"); if (!np) { dev_err(&pdev->dev, "failed to find charger node\n"); return NULL; @@ -452,13 +452,14 @@ max8925_power_dt_init(struct platform_device *pdev) pdata = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_pdata), GFP_KERNEL); + if (!pdata) + goto ret; of_property_read_u32(np, "topoff-threshold", &topoff_threshold); of_property_read_u32(np, "batt-detect", &batt_detect); of_property_read_u32(np, "fast-charge", &fast_charge); of_property_read_u32(np, "no-insert-detect", &no_insert_detect); of_property_read_u32(np, "no-temp-support", &no_temp_support); - of_node_put(np); pdata->batt_detect = batt_detect; pdata->fast_charge = fast_charge; @@ -466,6 +467,8 @@ max8925_power_dt_init(struct platform_device *pdev) pdata->no_insert_detect = no_insert_detect; pdata->no_temp_support = no_temp_support; +ret: + of_node_put(np); return pdata; } #else diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 078afd61490..6cb7fe5c022 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -58,7 +58,7 @@ static bool __power_supply_is_supplied_by(struct power_supply *supplier, static int __power_supply_changed_work(struct device *dev, void *data) { - struct power_supply *psy = (struct power_supply *)data; + struct power_supply *psy = data; struct power_supply *pst = dev_get_drvdata(dev); if (__power_supply_is_supplied_by(psy, pst)) { @@ -78,7 +78,14 @@ static void power_supply_changed_work(struct work_struct *work) dev_dbg(psy->dev, "%s\n", __func__); spin_lock_irqsave(&psy->changed_lock, flags); - if (psy->changed) { + /* + * Check 'changed' here to avoid issues due to race between + * power_supply_changed() and this routine. In worst case + * power_supply_changed() can be called again just before we take above + * lock. During the first call of this routine we will mark 'changed' as + * false and it will stay false for the next call as well. + */ + if (likely(psy->changed)) { psy->changed = false; spin_unlock_irqrestore(&psy->changed_lock, flags); class_for_each_device(power_supply_class, NULL, psy, @@ -89,12 +96,13 @@ static void power_supply_changed_work(struct work_struct *work) kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); spin_lock_irqsave(&psy->changed_lock, flags); } + /* - * Dependent power supplies (e.g. battery) may have changed state - * as a result of this event, so poll again and hold the - * wakeup_source until all events are processed. + * Hold the wakeup_source until all events are processed. + * power_supply_changed() might have called again and have set 'changed' + * to true. */ - if (!psy->changed) + if (likely(!psy->changed)) pm_relax(psy->dev); spin_unlock_irqrestore(&psy->changed_lock, flags); } @@ -119,7 +127,7 @@ EXPORT_SYMBOL_GPL(power_supply_changed); static int __power_supply_populate_supplied_from(struct device *dev, void *data) { - struct power_supply *psy = (struct power_supply *)data; + struct power_supply *psy = data; struct power_supply *epsy = dev_get_drvdata(dev); struct device_node *np; int i = 0; @@ -127,7 +135,7 @@ static int __power_supply_populate_supplied_from(struct device *dev, do { np = of_parse_phandle(psy->of_node, "power-supplies", i++); if (!np) - continue; + break; if (np == epsy->of_node) { dev_info(psy->dev, "%s: Found supply : %s\n", @@ -158,12 +166,12 @@ static int power_supply_populate_supplied_from(struct power_supply *psy) static int __power_supply_find_supply_from_node(struct device *dev, void *data) { - struct device_node *np = (struct device_node *)data; + struct device_node *np = data; struct power_supply *epsy = dev_get_drvdata(dev); - /* return error breaks out of class_for_each_device loop */ + /* returning non-zero breaks out of class_for_each_device loop */ if (epsy->of_node == np) - return -EINVAL; + return 1; return 0; } @@ -171,30 +179,21 @@ static int __power_supply_find_supply_from_node(struct device *dev, static int power_supply_find_supply_from_node(struct device_node *supply_node) { int error; - struct device *dev; - struct class_dev_iter iter; /* - * Use iterator to see if any other device is registered. - * This is required since class_for_each_device returns 0 - * if there are no devices registered. - */ - class_dev_iter_init(&iter, power_supply_class, NULL, NULL); - dev = class_dev_iter_next(&iter); - - if (!dev) - return -EPROBE_DEFER; - - /* - * We have to treat the return value as inverted, because if - * we return error on not found, then it won't continue looking. - * So we trick it by returning error on success to stop looking - * once the matching device is found. + * class_for_each_device() either returns its own errors or values + * returned by __power_supply_find_supply_from_node(). + * + * __power_supply_find_supply_from_node() will return 0 (no match) + * or 1 (match). + * + * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if + * it returned 0, or error as returned by it. */ error = class_for_each_device(power_supply_class, NULL, supply_node, __power_supply_find_supply_from_node); - return error ? 0 : -EPROBE_DEFER; + return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER; } static int power_supply_check_supplies(struct power_supply *psy) @@ -215,17 +214,21 @@ static int power_supply_check_supplies(struct power_supply *psy) np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); if (!np) - continue; + break; ret = power_supply_find_supply_from_node(np); + of_node_put(np); + if (ret) { - dev_dbg(psy->dev, "Failed to find supply, defer!\n"); - of_node_put(np); - return -EPROBE_DEFER; + dev_dbg(psy->dev, "Failed to find supply!\n"); + return ret; } - of_node_put(np); } while (np); + /* Missing valid "power-supplies" entries */ + if (cnt == 1) + return 0; + /* All supplies found, allocate char ** array for filling */ psy->supplied_from = devm_kzalloc(psy->dev, sizeof(psy->supplied_from), GFP_KERNEL); @@ -234,7 +237,7 @@ static int power_supply_check_supplies(struct power_supply *psy) return -ENOMEM; } - *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * cnt, + *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * (cnt - 1), GFP_KERNEL); if (!*psy->supplied_from) { dev_err(psy->dev, "Couldn't allocate memory for supply list\n"); @@ -253,14 +256,12 @@ static inline int power_supply_check_supplies(struct power_supply *psy) static int __power_supply_am_i_supplied(struct device *dev, void *data) { union power_supply_propval ret = {0,}; - struct power_supply *psy = (struct power_supply *)data; + struct power_supply *psy = data; struct power_supply *epsy = dev_get_drvdata(dev); if (__power_supply_is_supplied_by(epsy, psy)) - if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) { - if (ret.intval) - return ret.intval; - } + if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) + return ret.intval; return 0; } @@ -285,12 +286,10 @@ static int __power_supply_is_system_supplied(struct device *dev, void *data) unsigned int *count = data; (*count)++; - if (psy->type != POWER_SUPPLY_TYPE_BATTERY) { - if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) - return 0; - if (ret.intval) + if (psy->type != POWER_SUPPLY_TYPE_BATTERY) + if (!psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret)) return ret.intval; - } + return 0; } @@ -423,9 +422,7 @@ static int psy_register_thermal(struct power_supply *psy) if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { psy->tzd = thermal_zone_device_register(psy->name, 0, 0, psy, &psy_tzd_ops, NULL, 0, 0); - if (IS_ERR(psy->tzd)) - return PTR_ERR(psy->tzd); - break; + return PTR_ERR_OR_ZERO(psy->tzd); } } return 0; @@ -503,9 +500,7 @@ static int psy_register_cooler(struct power_supply *psy) psy->tcd = thermal_cooling_device_register( (char *)psy->name, psy, &psy_tcd_ops); - if (IS_ERR(psy->tcd)) - return PTR_ERR(psy->tcd); - break; + return PTR_ERR_OR_ZERO(psy->tcd); } } return 0; @@ -591,7 +586,7 @@ static int __power_supply_register(struct device *parent, power_supply_changed(psy); - goto success; + return 0; create_triggers_failed: psy_unregister_cooler(psy); @@ -604,7 +599,6 @@ wakeup_init_failed: check_supplies_failed: dev_set_name_failed: put_device(dev); -success: return rc; } diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c index 995f966ed5b..effa093c37b 100644 --- a/drivers/power/power_supply_leds.c +++ b/drivers/power/power_supply_leds.c @@ -57,8 +57,6 @@ static void power_supply_update_bat_leds(struct power_supply *psy) static int power_supply_create_bat_triggers(struct power_supply *psy) { - int rc = 0; - psy->charging_full_trig_name = kasprintf(GFP_KERNEL, "%s-charging-or-full", psy->name); if (!psy->charging_full_trig_name) @@ -87,7 +85,7 @@ static int power_supply_create_bat_triggers(struct power_supply *psy) led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, &psy->charging_blink_full_solid_trig); - goto success; + return 0; charging_blink_full_solid_failed: kfree(psy->full_trig_name); @@ -96,9 +94,7 @@ full_failed: charging_failed: kfree(psy->charging_full_trig_name); charging_full_failed: - rc = -ENOMEM; -success: - return rc; + return -ENOMEM; } static void power_supply_remove_bat_triggers(struct power_supply *psy) @@ -132,20 +128,13 @@ static void power_supply_update_gen_leds(struct power_supply *psy) static int power_supply_create_gen_triggers(struct power_supply *psy) { - int rc = 0; - psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", psy->name); if (!psy->online_trig_name) - goto online_failed; + return -ENOMEM; led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); - goto success; - -online_failed: - rc = -ENOMEM; -success: - return rc; + return 0; } static void power_supply_remove_gen_triggers(struct power_supply *psy) diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 750a2027566..62653f50a52 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -73,19 +73,20 @@ static ssize_t power_supply_show_property(struct device *dev, const ptrdiff_t off = attr - power_supply_attrs; union power_supply_propval value; - if (off == POWER_SUPPLY_PROP_TYPE) + if (off == POWER_SUPPLY_PROP_TYPE) { value.intval = psy->type; - else + } else { ret = psy->get_property(psy, off, &value); - if (ret < 0) { - if (ret == -ENODATA) - dev_dbg(dev, "driver has no data for `%s' property\n", - attr->attr.name); - else if (ret != -ENODEV) - dev_err(dev, "driver failed to report `%s' property: %zd\n", - attr->attr.name, ret); - return ret; + if (ret < 0) { + if (ret == -ENODATA) + dev_dbg(dev, "driver has no data for `%s' property\n", + attr->attr.name); + else if (ret != -ENODEV) + dev_err(dev, "driver failed to report `%s' property: %zd\n", + attr->attr.name, ret); + return ret; + } } if (off == POWER_SUPPLY_PROP_STATUS) @@ -149,9 +150,11 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(voltage_now), POWER_SUPPLY_ATTR(voltage_avg), POWER_SUPPLY_ATTR(voltage_ocv), + POWER_SUPPLY_ATTR(voltage_boot), POWER_SUPPLY_ATTR(current_max), POWER_SUPPLY_ATTR(current_now), POWER_SUPPLY_ATTR(current_avg), + POWER_SUPPLY_ATTR(current_boot), POWER_SUPPLY_ATTR(power_now), POWER_SUPPLY_ATTR(power_avg), POWER_SUPPLY_ATTR(charge_full_design), @@ -193,6 +196,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(type), POWER_SUPPLY_ATTR(scope), POWER_SUPPLY_ATTR(charge_term_current), + POWER_SUPPLY_ATTR(calibrate), /* Properties of type `const char *' */ POWER_SUPPLY_ATTR(model_name), POWER_SUPPLY_ATTR(manufacturer), diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 527a0f47ef4..f65ff49bb27 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -40,7 +40,7 @@ config POWER_RESET_AXXIA config POWER_RESET_BRCMSTB bool "Broadcom STB reset driver" if COMPILE_TEST - depends on POWER_RESET && ARM + depends on ARM default ARCH_BRCMSTB help This driver provides restart support for ARM-based Broadcom STB @@ -57,9 +57,17 @@ config POWER_RESET_GPIO If your board needs a GPIO high/low to power down, say Y and create a binding in your devicetree. +config POWER_RESET_GPIO_RESTART + bool "GPIO restart driver" + depends on OF_GPIO + help + This driver supports restarting your board via a GPIO line. + If your board needs a GPIO high/low to restart, say Y and + create a binding in your devicetree. + config POWER_RESET_HISI bool "Hisilicon power-off driver" - depends on POWER_RESET && ARCH_HISI + depends on ARCH_HISI help Reboot support for Hisilicon boards. @@ -69,6 +77,13 @@ config POWER_RESET_MSM help Power off and restart support for Qualcomm boards. +config POWER_RESET_LTC2952 + bool "LTC2952 PowerPath power-off driver" + depends on OF_GPIO + help + This driver supports an external powerdown trigger and board power + down via the LTC2952. Bindings are made in the device tree. + config POWER_RESET_QNAP bool "QNAP power-off driver" depends on OF_GPIO && PLAT_ORION @@ -92,6 +107,12 @@ config POWER_RESET_SUN6I help Reboot support for the Allwinner A31 SoCs. +config POWER_RESET_ST + bool "ST restart power-off driver" + depends on ARCH_STI + help + Power off and reset support for STMicroelectronics boards. + config POWER_RESET_VERSATILE bool "ARM Versatile family reboot driver" depends on ARM @@ -122,4 +143,12 @@ config POWER_RESET_KEYSTONE help Reboot support for the KEYSTONE SoCs. +config POWER_RESET_SYSCON + bool "Generic SYSCON regmap reset driver" + depends on OF + select MFD_SYSCON + help + Reboot support for generic SYSCON mapped register reset. + endif + diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 73221009f2b..76ce1c59469 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -4,12 +4,16 @@ obj-$(CONFIG_POWER_RESET_AT91_RESET) += at91-reset.o obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o +obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-reboot.o +obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o +obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c new file mode 100644 index 00000000000..a76829b3f1c --- /dev/null +++ b/drivers/power/reset/gpio-restart.c @@ -0,0 +1,149 @@ +/* + * Toggles a GPIO pin to restart a device + * + * Copyright (C) 2014 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * Based on the gpio-poweroff driver. + */ +#include <linux/reboot.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/gpio/consumer.h> +#include <linux/of_platform.h> +#include <linux/module.h> + +struct gpio_restart { + struct gpio_desc *reset_gpio; + struct notifier_block restart_handler; + u32 active_delay_ms; + u32 inactive_delay_ms; + u32 wait_delay_ms; +}; + +static int gpio_restart_notify(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct gpio_restart *gpio_restart = + container_of(this, struct gpio_restart, restart_handler); + + /* drive it active, also inactive->active edge */ + gpiod_direction_output(gpio_restart->reset_gpio, 1); + mdelay(gpio_restart->active_delay_ms); + + /* drive inactive, also active->inactive edge */ + gpiod_set_value(gpio_restart->reset_gpio, 0); + mdelay(gpio_restart->inactive_delay_ms); + + /* drive it active, also inactive->active edge */ + gpiod_set_value(gpio_restart->reset_gpio, 1); + + /* give it some time */ + mdelay(gpio_restart->wait_delay_ms); + + WARN_ON(1); + + return NOTIFY_DONE; +} + +static int gpio_restart_probe(struct platform_device *pdev) +{ + struct gpio_restart *gpio_restart; + bool open_source = false; + u32 property; + int ret; + + gpio_restart = devm_kzalloc(&pdev->dev, sizeof(*gpio_restart), + GFP_KERNEL); + if (!gpio_restart) + return -ENOMEM; + + open_source = of_property_read_bool(pdev->dev.of_node, "open-source"); + + gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL, + open_source ? GPIOD_IN : GPIOD_OUT_LOW); + if (IS_ERR(gpio_restart->reset_gpio)) { + dev_err(&pdev->dev, "Could net get reset GPIO\n"); + return PTR_ERR(gpio_restart->reset_gpio); + } + + gpio_restart->restart_handler.notifier_call = gpio_restart_notify; + gpio_restart->restart_handler.priority = 128; + gpio_restart->active_delay_ms = 100; + gpio_restart->inactive_delay_ms = 100; + gpio_restart->wait_delay_ms = 3000; + + ret = of_property_read_u32(pdev->dev.of_node, "priority", &property); + if (!ret) { + if (property > 255) + dev_err(&pdev->dev, "Invalid priority property: %u\n", + property); + else + gpio_restart->restart_handler.priority = property; + } + + of_property_read_u32(pdev->dev.of_node, "active-delay", + &gpio_restart->active_delay_ms); + of_property_read_u32(pdev->dev.of_node, "inactive-delay", + &gpio_restart->inactive_delay_ms); + of_property_read_u32(pdev->dev.of_node, "wait-delay", + &gpio_restart->wait_delay_ms); + + platform_set_drvdata(pdev, gpio_restart); + + ret = register_restart_handler(&gpio_restart->restart_handler); + if (ret) { + dev_err(&pdev->dev, "%s: cannot register restart handler, %d\n", + __func__, ret); + return -ENODEV; + } + + return 0; +} + +static int gpio_restart_remove(struct platform_device *pdev) +{ + struct gpio_restart *gpio_restart = platform_get_drvdata(pdev); + int ret; + + ret = unregister_restart_handler(&gpio_restart->restart_handler); + if (ret) { + dev_err(&pdev->dev, + "%s: cannot unregister restart handler, %d\n", + __func__, ret); + return -ENODEV; + } + + return 0; +} + +static const struct of_device_id of_gpio_restart_match[] = { + { .compatible = "gpio-restart", }, + {}, +}; + +static struct platform_driver gpio_restart_driver = { + .probe = gpio_restart_probe, + .remove = gpio_restart_remove, + .driver = { + .name = "restart-gpio", + .owner = THIS_MODULE, + .of_match_table = of_gpio_restart_match, + }, +}; + +module_platform_driver(gpio_restart_driver); + +MODULE_AUTHOR("David Riley <davidriley@chromium.org>"); +MODULE_DESCRIPTION("GPIO restart driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c new file mode 100644 index 00000000000..116a1cef8f7 --- /dev/null +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -0,0 +1,386 @@ +/* + * LTC2952 (PowerPath) driver + * + * Copyright (C) 2014, Xsens Technologies BV <info@xsens.com> + * Maintainer: René Moll <linux@r-moll.nl> + * + * 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. + * + * ---------------------------------------- + * - Description + * ---------------------------------------- + * + * This driver is to be used with an external PowerPath Controller (LTC2952). + * Its function is to determine when a external shut down is triggered + * and react by properly shutting down the system. + * + * This driver expects a device tree with a ltc2952 entry for pin mapping. + * + * ---------------------------------------- + * - GPIO + * ---------------------------------------- + * + * The following GPIOs are used: + * - trigger (input) + * A level change indicates the shut-down trigger. If it's state reverts + * within the time-out defined by trigger_delay, the shut down is not + * executed. + * + * - watchdog (output) + * Once a shut down is triggered, the driver will toggle this signal, + * with an internal (wde_interval) to stall the hardware shut down. + * + * - kill (output) + * The last action during shut down is triggering this signalling, such + * that the PowerPath Control will power down the hardware. + * + * ---------------------------------------- + * - Interrupts + * ---------------------------------------- + * + * The driver requires a non-shared, edge-triggered interrupt on the trigger + * GPIO. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/ktime.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/reboot.h> + +struct ltc2952_poweroff_data { + struct hrtimer timer_trigger; + struct hrtimer timer_wde; + + ktime_t trigger_delay; + ktime_t wde_interval; + + struct device *dev; + + unsigned int virq; + + /** + * 0: trigger + * 1: watchdog + * 2: kill + */ + struct gpio_desc *gpio[3]; +}; + +static int ltc2952_poweroff_panic; +static struct ltc2952_poweroff_data *ltc2952_data; + +#define POWERPATH_IO_TRIGGER 0 +#define POWERPATH_IO_WATCHDOG 1 +#define POWERPATH_IO_KILL 2 + +/** + * ltc2952_poweroff_timer_wde - Timer callback + * Toggles the watchdog reset signal each wde_interval + * + * @timer: corresponding timer + * + * Returns HRTIMER_RESTART for an infinite loop which will only stop when the + * machine actually shuts down + */ +static enum hrtimer_restart ltc2952_poweroff_timer_wde(struct hrtimer *timer) +{ + ktime_t now; + int state; + unsigned long overruns; + + if (ltc2952_poweroff_panic) + return HRTIMER_NORESTART; + + state = gpiod_get_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG]); + gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], !state); + + now = hrtimer_cb_get_time(timer); + overruns = hrtimer_forward(timer, now, ltc2952_data->wde_interval); + + return HRTIMER_RESTART; +} + +static enum hrtimer_restart ltc2952_poweroff_timer_trigger( + struct hrtimer *timer) +{ + int ret; + + ret = hrtimer_start(<c2952_data->timer_wde, + ltc2952_data->wde_interval, HRTIMER_MODE_REL); + + if (ret) { + dev_err(ltc2952_data->dev, "unable to start the timer\n"); + /* + * The device will not toggle the watchdog reset, + * thus shut down is only safe if the PowerPath controller + * has a long enough time-off before triggering a hardware + * power-off. + * + * Only sending a warning as the system will power-off anyway + */ + } + + dev_info(ltc2952_data->dev, "executing shutdown\n"); + + orderly_poweroff(true); + + return HRTIMER_NORESTART; +} + +/** + * ltc2952_poweroff_handler - Interrupt handler + * Triggered each time the trigger signal changes state and (de)activates a + * time-out (timer_trigger). Once the time-out is actually reached the shut + * down is executed. + * + * @irq: IRQ number + * @dev_id: pointer to the main data structure + */ +static irqreturn_t ltc2952_poweroff_handler(int irq, void *dev_id) +{ + int ret; + struct ltc2952_poweroff_data *data = dev_id; + + if (ltc2952_poweroff_panic) + goto irq_ok; + + if (hrtimer_active(&data->timer_wde)) { + /* shutdown is already triggered, nothing to do any more */ + goto irq_ok; + } + + if (!hrtimer_active(&data->timer_trigger)) { + ret = hrtimer_start(&data->timer_trigger, data->trigger_delay, + HRTIMER_MODE_REL); + + if (ret) + dev_err(data->dev, "unable to start the wait timer\n"); + } else { + ret = hrtimer_cancel(&data->timer_trigger); + /* omitting return value check, timer should have been valid */ + } + +irq_ok: + return IRQ_HANDLED; +} + +static void ltc2952_poweroff_kill(void) +{ + gpiod_set_value(ltc2952_data->gpio[POWERPATH_IO_KILL], 1); +} + +static int ltc2952_poweroff_suspend(struct platform_device *pdev, + pm_message_t state) +{ + return -ENOSYS; +} + +static int ltc2952_poweroff_resume(struct platform_device *pdev) +{ + return -ENOSYS; +} + +static void ltc2952_poweroff_default(struct ltc2952_poweroff_data *data) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(data->gpio); i++) + data->gpio[i] = NULL; + + data->wde_interval = ktime_set(0, 300L*1E6L); + data->trigger_delay = ktime_set(2, 500L*1E6L); + + hrtimer_init(&data->timer_trigger, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->timer_trigger.function = <c2952_poweroff_timer_trigger; + + hrtimer_init(&data->timer_wde, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->timer_wde.function = <c2952_poweroff_timer_wde; +} + +static int ltc2952_poweroff_init(struct platform_device *pdev) +{ + int ret, virq; + unsigned int i; + struct ltc2952_poweroff_data *data; + + static char *name[] = { + "trigger", + "watchdog", + "kill", + NULL + }; + + data = ltc2952_data; + ltc2952_poweroff_default(ltc2952_data); + + for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) { + ltc2952_data->gpio[i] = gpiod_get(&pdev->dev, name[i]); + + if (IS_ERR(ltc2952_data->gpio[i])) { + ret = PTR_ERR(ltc2952_data->gpio[i]); + dev_err(&pdev->dev, + "unable to claim the following gpio: %s\n", + name[i]); + goto err_io; + } + } + + ret = gpiod_direction_output( + ltc2952_data->gpio[POWERPATH_IO_WATCHDOG], 0); + if (ret) { + dev_err(&pdev->dev, "unable to use watchdog-gpio as output\n"); + goto err_io; + } + + ret = gpiod_direction_output(ltc2952_data->gpio[POWERPATH_IO_KILL], 0); + if (ret) { + dev_err(&pdev->dev, "unable to use kill-gpio as output\n"); + goto err_io; + } + + virq = gpiod_to_irq(ltc2952_data->gpio[POWERPATH_IO_TRIGGER]); + if (virq < 0) { + dev_err(&pdev->dev, "cannot map GPIO as interrupt"); + goto err_io; + } + + ltc2952_data->virq = virq; + ret = request_irq(virq, + ltc2952_poweroff_handler, + (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING), + "ltc2952-poweroff", + ltc2952_data + ); + + if (ret) { + dev_err(&pdev->dev, "cannot configure an interrupt handler\n"); + goto err_io; + } + + return 0; + +err_io: + for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) + if (ltc2952_data->gpio[i]) + gpiod_put(ltc2952_data->gpio[i]); + + return ret; +} + +static int ltc2952_poweroff_probe(struct platform_device *pdev) +{ + int ret; + + if (pm_power_off) { + dev_err(&pdev->dev, "pm_power_off already registered"); + return -EBUSY; + } + + ltc2952_data = kzalloc(sizeof(*ltc2952_data), GFP_KERNEL); + if (!ltc2952_data) + return -ENOMEM; + + ltc2952_data->dev = &pdev->dev; + + ret = ltc2952_poweroff_init(pdev); + if (ret) + goto err; + + pm_power_off = <c2952_poweroff_kill; + + dev_info(&pdev->dev, "probe successful\n"); + + return 0; + +err: + kfree(ltc2952_data); + return ret; +} + +static int ltc2952_poweroff_remove(struct platform_device *pdev) +{ + unsigned int i; + + pm_power_off = NULL; + + if (ltc2952_data) { + free_irq(ltc2952_data->virq, ltc2952_data); + + for (i = 0; i < ARRAY_SIZE(ltc2952_data->gpio); i++) + gpiod_put(ltc2952_data->gpio[i]); + + kfree(ltc2952_data); + } + + return 0; +} + +static const struct of_device_id of_ltc2952_poweroff_match[] = { + { .compatible = "lltc,ltc2952"}, + {}, +}; +MODULE_DEVICE_TABLE(of, of_ltc2952_poweroff_match); + +static struct platform_driver ltc2952_poweroff_driver = { + .probe = ltc2952_poweroff_probe, + .remove = ltc2952_poweroff_remove, + .driver = { + .name = "ltc2952-poweroff", + .owner = THIS_MODULE, + .of_match_table = of_ltc2952_poweroff_match, + }, + .suspend = ltc2952_poweroff_suspend, + .resume = ltc2952_poweroff_resume, +}; + +static int ltc2952_poweroff_notify_panic(struct notifier_block *nb, + unsigned long code, void *unused) +{ + ltc2952_poweroff_panic = 1; + return NOTIFY_DONE; +} + +static struct notifier_block ltc2952_poweroff_panic_nb = { + .notifier_call = ltc2952_poweroff_notify_panic, +}; + +static int __init ltc2952_poweroff_platform_init(void) +{ + ltc2952_poweroff_panic = 0; + + atomic_notifier_chain_register(&panic_notifier_list, + <c2952_poweroff_panic_nb); + + return platform_driver_register(<c2952_poweroff_driver); +} + +static void __exit ltc2952_poweroff_platform_exit(void) +{ + atomic_notifier_chain_unregister(&panic_notifier_list, + <c2952_poweroff_panic_nb); + + platform_driver_unregister(<c2952_poweroff_driver); +} + +module_init(ltc2952_poweroff_platform_init); +module_exit(ltc2952_poweroff_platform_exit); + +MODULE_AUTHOR("René Moll <rene.moll@xsens.com>"); +MODULE_DESCRIPTION("LTC PowerPath power-off driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c index 774f9a3b310..4702efdfe46 100644 --- a/drivers/power/reset/msm-poweroff.c +++ b/drivers/power/reset/msm-poweroff.c @@ -20,21 +20,27 @@ #include <linux/platform_device.h> #include <linux/module.h> #include <linux/reboot.h> - -#include <asm/system_misc.h> +#include <linux/pm.h> static void __iomem *msm_ps_hold; - -static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd) +static int do_msm_restart(struct notifier_block *nb, unsigned long action, + void *data) { writel(0, msm_ps_hold); mdelay(10000); + + return NOTIFY_DONE; } +static struct notifier_block restart_nb = { + .notifier_call = do_msm_restart, + .priority = 128, +}; + static void do_msm_poweroff(void) { /* TODO: Add poweroff capability */ - do_msm_restart(REBOOT_HARD, NULL); + do_msm_restart(&restart_nb, 0, NULL); } static int msm_restart_probe(struct platform_device *pdev) @@ -47,8 +53,10 @@ static int msm_restart_probe(struct platform_device *pdev) if (IS_ERR(msm_ps_hold)) return PTR_ERR(msm_ps_hold); + register_restart_handler(&restart_nb); + pm_power_off = do_msm_poweroff; - arm_pm_restart = do_msm_restart; + return 0; } diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c new file mode 100644 index 00000000000..a0acf25ee2a --- /dev/null +++ b/drivers/power/reset/st-poweroff.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 STMicroelectronics + * + * Power off Restart driver, used in STMicroelectronics devices. + * + * Author: Christophe Kerello <christophe.kerello@st.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> + +#include <asm/system_misc.h> + +struct reset_syscfg { + struct regmap *regmap; + /* syscfg used for reset */ + unsigned int offset_rst; + unsigned int mask_rst; + /* syscfg used for unmask the reset */ + unsigned int offset_rst_msk; + unsigned int mask_rst_msk; +}; + +/* STiH415 */ +#define STIH415_SYSCFG_11 0x2c +#define STIH415_SYSCFG_15 0x3c + +static struct reset_syscfg stih415_reset = { + .offset_rst = STIH415_SYSCFG_11, + .mask_rst = BIT(0), + .offset_rst_msk = STIH415_SYSCFG_15, + .mask_rst_msk = BIT(0) +}; + +/* STiH416 */ +#define STIH416_SYSCFG_500 0x7d0 +#define STIH416_SYSCFG_504 0x7e0 + +static struct reset_syscfg stih416_reset = { + .offset_rst = STIH416_SYSCFG_500, + .mask_rst = BIT(0), + .offset_rst_msk = STIH416_SYSCFG_504, + .mask_rst_msk = BIT(0) +}; + +/* STiH407 */ +#define STIH407_SYSCFG_4000 0x0 +#define STIH407_SYSCFG_4008 0x20 + +static struct reset_syscfg stih407_reset = { + .offset_rst = STIH407_SYSCFG_4000, + .mask_rst = BIT(0), + .offset_rst_msk = STIH407_SYSCFG_4008, + .mask_rst_msk = BIT(0) +}; + +/* STiD127 */ +#define STID127_SYSCFG_700 0x0 +#define STID127_SYSCFG_773 0x124 + +static struct reset_syscfg stid127_reset = { + .offset_rst = STID127_SYSCFG_773, + .mask_rst = BIT(0), + .offset_rst_msk = STID127_SYSCFG_700, + .mask_rst_msk = BIT(8) +}; + +static struct reset_syscfg *st_restart_syscfg; + +static void st_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + /* reset syscfg updated */ + regmap_update_bits(st_restart_syscfg->regmap, + st_restart_syscfg->offset_rst, + st_restart_syscfg->mask_rst, + 0); + + /* unmask the reset */ + regmap_update_bits(st_restart_syscfg->regmap, + st_restart_syscfg->offset_rst_msk, + st_restart_syscfg->mask_rst_msk, + 0); +} + +static struct of_device_id st_reset_of_match[] = { + { + .compatible = "st,stih415-restart", + .data = (void *)&stih415_reset, + }, { + .compatible = "st,stih416-restart", + .data = (void *)&stih416_reset, + }, { + .compatible = "st,stih407-restart", + .data = (void *)&stih407_reset, + }, { + .compatible = "st,stid127-restart", + .data = (void *)&stid127_reset, + }, + {} +}; + +static int st_reset_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + struct device *dev = &pdev->dev; + + match = of_match_device(st_reset_of_match, dev); + if (!match) + return -ENODEV; + + st_restart_syscfg = (struct reset_syscfg *)match->data; + + st_restart_syscfg->regmap = + syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(st_restart_syscfg->regmap)) { + dev_err(dev, "No syscfg phandle specified\n"); + return PTR_ERR(st_restart_syscfg->regmap); + } + + arm_pm_restart = st_restart; + + return 0; +} + +static struct platform_driver st_reset_driver = { + .probe = st_reset_probe, + .driver = { + .name = "st_reset", + .of_match_table = st_reset_of_match, + }, +}; + +static int __init st_reset_init(void) +{ + return platform_driver_register(&st_reset_driver); +} + +device_initcall(st_reset_init); + +MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics Power off Restart driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c new file mode 100644 index 00000000000..815b901822c --- /dev/null +++ b/drivers/power/reset/syscon-reboot.c @@ -0,0 +1,91 @@ +/* + * Generic Syscon Reboot Driver + * + * Copyright (c) 2013, Applied Micro Circuits Corporation + * Author: Feng Kan <fkan@apm.com> + * + * 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 <linux/delay.h> +#include <linux/io.h> +#include <linux/notifier.h> +#include <linux/mfd/syscon.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +struct syscon_reboot_context { + struct regmap *map; + u32 offset; + u32 mask; + struct notifier_block restart_handler; +}; + +static int syscon_restart_handle(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct syscon_reboot_context *ctx = + container_of(this, struct syscon_reboot_context, + restart_handler); + + /* Issue the reboot */ + regmap_write(ctx->map, ctx->offset, ctx->mask); + + mdelay(1000); + + pr_emerg("Unable to restart system\n"); + return NOTIFY_DONE; +} + +static int syscon_reboot_probe(struct platform_device *pdev) +{ + struct syscon_reboot_context *ctx; + struct device *dev = &pdev->dev; + int err; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->map = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap"); + if (IS_ERR(ctx->map)) + return PTR_ERR(ctx->map); + + if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset)) + return -EINVAL; + + if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask)) + return -EINVAL; + + ctx->restart_handler.notifier_call = syscon_restart_handle; + ctx->restart_handler.priority = 128; + err = register_restart_handler(&ctx->restart_handler); + if (err) + dev_err(dev, "can't register restart notifier (err=%d)\n", err); + + return err; +} + +static struct of_device_id syscon_reboot_of_match[] = { + { .compatible = "syscon-reboot" }, + {} +}; + +static struct platform_driver syscon_reboot_driver = { + .probe = syscon_reboot_probe, + .driver = { + .name = "syscon-reboot", + .of_match_table = syscon_reboot_of_match, + }, +}; +module_platform_driver(syscon_reboot_driver); diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c index ecd55f81b9d..6b49be6867a 100644 --- a/drivers/power/reset/xgene-reboot.c +++ b/drivers/power/reset/xgene-reboot.c @@ -40,7 +40,7 @@ struct xgene_reboot_context { static struct xgene_reboot_context *xgene_restart_ctx; -static void xgene_restart(char str, const char *cmd) +static void xgene_restart(enum reboot_mode mode, const char *cmd) { struct xgene_reboot_context *ctx = xgene_restart_ctx; unsigned long timeout; diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index b5f2a76b6cd..c7b7b4018df 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -48,7 +48,10 @@ enum { REG_FULL_CHARGE_CAPACITY_CHARGE, REG_DESIGN_CAPACITY, REG_DESIGN_CAPACITY_CHARGE, - REG_DESIGN_VOLTAGE, + REG_DESIGN_VOLTAGE_MIN, + REG_DESIGN_VOLTAGE_MAX, + REG_MANUFACTURER, + REG_MODEL_NAME, }; /* Battery Mode defines */ @@ -68,6 +71,7 @@ enum sbs_battery_mode { #define BATTERY_FULL_CHARGED 0x20 #define BATTERY_FULL_DISCHARGED 0x10 +/* min_value and max_value are only valid for numerical data */ #define SBS_DATA(_psp, _addr, _min_value, _max_value) { \ .psp = _psp, \ .addr = _addr, \ @@ -111,10 +115,17 @@ static const struct chip_data { SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535), [REG_DESIGN_CAPACITY_CHARGE] = SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535), - [REG_DESIGN_VOLTAGE] = + [REG_DESIGN_VOLTAGE_MIN] = + SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 0x19, 0, 65535), + [REG_DESIGN_VOLTAGE_MAX] = SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535), [REG_SERIAL_NUMBER] = SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535), + /* Properties of type `const char *' */ + [REG_MANUFACTURER] = + SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535), + [REG_MODEL_NAME] = + SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535) }; static enum power_supply_property sbs_properties[] = { @@ -130,6 +141,7 @@ static enum power_supply_property sbs_properties[] = { POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, POWER_SUPPLY_PROP_ENERGY_NOW, POWER_SUPPLY_PROP_ENERGY_FULL, @@ -137,6 +149,9 @@ static enum power_supply_property sbs_properties[] = { POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + /* Properties of type `const char *' */ + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME }; struct sbs_info { @@ -153,6 +168,9 @@ struct sbs_info { int ignore_changes; }; +static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; +static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; + static int sbs_read_word_data(struct i2c_client *client, u8 address) { struct sbs_info *chip = i2c_get_clientdata(client); @@ -179,6 +197,74 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) return le16_to_cpu(ret); } +static int sbs_read_string_data(struct i2c_client *client, u8 address, + char *values) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + s32 ret = 0, block_length = 0; + int retries_length = 1, retries_block = 1; + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + + if (chip->pdata) { + retries_length = max(chip->pdata->i2c_retry_count + 1, 1); + retries_block = max(chip->pdata->i2c_retry_count + 1, 1); + } + + /* Adapter needs to support these two functions */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_I2C_BLOCK)){ + return -ENODEV; + } + + /* Get the length of block data */ + while (retries_length > 0) { + ret = i2c_smbus_read_byte_data(client, address); + if (ret >= 0) + break; + retries_length--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + /* block_length does not include NULL terminator */ + block_length = ret; + if (block_length > I2C_SMBUS_BLOCK_MAX) { + dev_err(&client->dev, + "%s: Returned block_length is longer than 0x%x\n", + __func__, I2C_SMBUS_BLOCK_MAX); + return -EINVAL; + } + + /* Get the block data */ + while (retries_block > 0) { + ret = i2c_smbus_read_i2c_block_data( + client, address, + block_length + 1, block_buffer); + if (ret >= 0) + break; + retries_block--; + } + + if (ret < 0) { + dev_dbg(&client->dev, + "%s: i2c read at address 0x%x failed\n", + __func__, address); + return ret; + } + + /* block_buffer[0] == block_length */ + memcpy(values, block_buffer + 1, block_length); + values[block_length] = '\0'; + + return le16_to_cpu(ret); +} + static int sbs_write_word_data(struct i2c_client *client, u8 address, u16 value) { @@ -318,6 +404,19 @@ static int sbs_get_battery_property(struct i2c_client *client, return 0; } +static int sbs_get_battery_string_property(struct i2c_client *client, + int reg_offset, enum power_supply_property psp, char *val) +{ + s32 ret; + + ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val); + + if (ret < 0) + return ret; + + return 0; +} + static void sbs_unit_adjustment(struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) { @@ -336,6 +435,7 @@ static void sbs_unit_adjustment(struct i2c_client *client, break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: case POWER_SUPPLY_PROP_CURRENT_NOW: case POWER_SUPPLY_PROP_CHARGE_NOW: @@ -497,6 +597,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: ret = sbs_get_property_index(client, psp); if (ret < 0) @@ -505,6 +606,26 @@ static int sbs_get_property(struct power_supply *psy, ret = sbs_get_battery_property(client, ret, psp, val); break; + case POWER_SUPPLY_PROP_MODEL_NAME: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_string_property(client, ret, psp, + model_name); + val->strval = model_name; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = sbs_get_property_index(client, psp); + if (ret < 0) + break; + + ret = sbs_get_battery_string_property(client, ret, psp, + manufacturer); + val->strval = manufacturer; + break; + default: dev_err(&client->dev, "%s: INVALID property\n", __func__); diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c index 5d9c605cf53..0ff5a20ac95 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -22,42 +22,6 @@ #include <linux/mfd/max14577-private.h> #include <linux/regulator/of_regulator.h> -/* - * Valid limits of current for max14577 and max77836 chargers. - * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 - * register for given chipset. - */ -struct maxim_charger_current { - /* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ - unsigned int min; - /* - * Minimal current when high setting is active, - * set in CHGCTRL4/MBCICHWRCH, uA - */ - unsigned int high_start; - /* Value of one step in high setting, uA */ - unsigned int high_step; - /* Maximum current of high setting, uA */ - unsigned int max; -}; - -/* Table of valid charger currents for different Maxim chipsets */ -static const struct maxim_charger_current maxim_charger_currents[] = { - [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, - [MAXIM_DEVICE_TYPE_MAX14577] = { - .min = MAX14577_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX14577_REGULATOR_CURRENT_LIMIT_MAX, - }, - [MAXIM_DEVICE_TYPE_MAX77836] = { - .min = MAX77836_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX77836_REGULATOR_CURRENT_LIMIT_MAX, - }, -}; - static int max14577_reg_is_enabled(struct regulator_dev *rdev) { int rid = rdev_get_id(rdev); @@ -103,8 +67,8 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) static int max14577_reg_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) { - int i, current_bits = 0xf; u8 reg_data; + int ret; struct max14577 *max14577 = rdev_get_drvdata(rdev); const struct maxim_charger_current *limits = &maxim_charger_currents[max14577->dev_type]; @@ -112,35 +76,9 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; - if (min_uA > limits->max || max_uA < limits->min) - return -EINVAL; - - if (max_uA < limits->high_start) { - /* - * Less than high_start, - * so set the minimal current (turn only Low Bit off) - */ - u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; - return max14577_update_reg(rdev->regmap, - MAX14577_CHG_REG_CHG_CTRL4, - CHGCTRL4_MBCICHWRCL_MASK, reg_data); - } - - /* - * max_uA is in range: <high_start, inifinite>, so search for - * valid current starting from maximum current. - */ - for (i = limits->max; i >= limits->high_start; i -= limits->high_step) { - if (i <= max_uA) - break; - current_bits--; - } - BUG_ON(current_bits < 0); /* Cannot happen */ - - /* Turn Low Bit on (use range high_start-max)... */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - /* and set proper High Bits */ - reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + ret = maxim_charger_calc_reg_current(limits, min_uA, max_uA, ®_data); + if (ret) + return ret; return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, @@ -442,16 +380,6 @@ static struct platform_driver max14577_regulator_driver = { static int __init max14577_regulator_init(void) { - /* Check for valid values for charger */ - BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX14577_REGULATOR_CURRENT_LIMIT_MAX); - BUILD_BUG_ON(MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX77836_REGULATOR_CURRENT_LIMIT_MAX); - /* Valid charger current values must be provided for each chipset */ - BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); - BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 583ccdb2c58..4a9d666f1e9 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -723,14 +723,14 @@ static int vfio_iommu_type1_attach_group(void *iommu_data, list_add(&group->next, &domain->group_list); if (!allow_unsafe_interrupts && - !iommu_domain_has_cap(domain->domain, IOMMU_CAP_INTR_REMAP)) { + !iommu_capable(bus, IOMMU_CAP_INTR_REMAP)) { pr_warn("%s: No interrupt remapping support. Use the module param \"allow_unsafe_interrupts\" to enable VFIO IOMMU support on this platform\n", __func__); ret = -EPERM; goto out_detach; } - if (iommu_domain_has_cap(domain->domain, IOMMU_CAP_CACHE_COHERENCY)) + if (iommu_capable(bus, IOMMU_CAP_CACHE_COHERENCY)) domain->prot |= IOMMU_CACHE; /* |